mapsmith 0.7.0__tar.gz → 0.8.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.
- {mapsmith-0.7.0 → mapsmith-0.8.0}/.gitignore +1 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/.zenodo.json +1 -1
- {mapsmith-0.7.0 → mapsmith-0.8.0}/CHANGELOG.md +192 -1
- {mapsmith-0.7.0 → mapsmith-0.8.0}/CITATION.cff +2 -2
- {mapsmith-0.7.0 → mapsmith-0.8.0}/CLAUDE.md +1 -1
- mapsmith-0.7.0/README.md → mapsmith-0.8.0/PKG-INFO +1387 -1298
- mapsmith-0.7.0/PKG-INFO → mapsmith-0.8.0/README.md +1344 -1340
- {mapsmith-0.7.0 → mapsmith-0.8.0}/SECURITY.md +34 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/docs/argleton-run.json +11 -14
- {mapsmith-0.7.0 → mapsmith-0.8.0}/docs/benchmarks.md +7 -6
- {mapsmith-0.7.0 → mapsmith-0.8.0}/docs/catalog-entry-spec.md +10 -9
- {mapsmith-0.7.0 → mapsmith-0.8.0}/docs/manifest-vocabulary.md +179 -179
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/01_verified_geoprocessing.ipynb +25 -25
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/02_terrain_hydrology.ipynb +36 -36
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/03_validated_plans.ipynb +23 -23
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/04_copernicus_terrain_vegetation.ipynb +31 -31
- {mapsmith-0.7.0 → mapsmith-0.8.0}/funding.json +1 -1
- {mapsmith-0.7.0 → mapsmith-0.8.0}/pyproject.toml +35 -3
- {mapsmith-0.7.0 → mapsmith-0.8.0}/schema/operation.schema.json +13 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/server.json +3 -3
- {mapsmith-0.7.0 → mapsmith-0.8.0}/site/build.py +20 -5
- {mapsmith-0.7.0 → mapsmith-0.8.0}/site/index.template.html +22 -15
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/__init__.py +1 -1
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/catalog.py +180 -25
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/duckdb_engine.py +4 -2
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/linework.py +40 -15
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/network.py +20 -8
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/raster.py +23 -8
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/sampling.py +23 -4
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/sedona_engine.py +4 -2
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/spatial_stats.py +301 -14
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/vector.py +3415 -3360
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/whitebox_engine.py +6 -4
- mapsmith-0.8.0/src/mapsmith/limits.py +58 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/lineage.py +273 -34
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/plans/registry.py +14 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/plans/validator.py +30 -1
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/provenance.py +278 -32
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/readers.py +21 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/server.py +41 -5
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/conftest.py +50 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/data/manifest-v1.schema.json +20 -4
- mapsmith-0.8.0/tests/data/manifest_spec_multi_file_digest.py +84 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/data/manifest_spec_validator.py +400 -386
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_catalog_applicability.py +11 -1
- mapsmith-0.8.0/tests/test_choice_at_scale.py +48 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_contours_and_cost.py +40 -1
- mapsmith-0.8.0/tests/test_dataset_digest.py +214 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_discovery_contract.py +7 -3
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_discovery_report.py +7 -4
- mapsmith-0.8.0/tests/test_extract_from_gdb.py +89 -0
- mapsmith-0.8.0/tests/test_geometry_declarations.py +268 -0
- mapsmith-0.8.0/tests/test_limits.py +74 -0
- mapsmith-0.8.0/tests/test_lineage_digest_rules.py +269 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_linework.py +35 -1
- mapsmith-0.8.0/tests/test_output_guard.py +128 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_plans.py +21 -1
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_published_figures.py +27 -11
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_raster.py +32 -4
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_redaction.py +38 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_showcase.py +87 -3
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_spatial_stats.py +226 -1
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_verify.py +2513 -2513
- {mapsmith-0.7.0 → mapsmith-0.8.0}/.dockerignore +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/CONTRIBUTING.md +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/Dockerfile +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/LICENSE +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/MANIFESTO.md +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/TRADEMARKS.md +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/docker-compose.yml +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/README.md +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/fixtures/COPERNICUS-NOTICE.md +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/fixtures/fetch_copernicus.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/fixtures/monte_baldo_dem.tif +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/fixtures/monte_baldo_s2.tif +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/examples/fixtures/mount_st_helens_dem.tif +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/glama.json +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/antimeridian.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/datum.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/discovery_log.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/__init__.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/dispatch.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/esri.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/geodesy.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/engines/summaries.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/gdal_policy.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/grid.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/jobs.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/plans/__init__.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/plans/executor.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/plans/models.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/preview.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/retrieval.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/sql_policy.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/stacks.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/ui.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/verify.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/src/mapsmith/workspace.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/data/discovery_queries.json +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_ambiguous_layer.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_antimeridian.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_band_math.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_catalog.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_catalog_entry_spec.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_catalog_retrieval.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_centroid_convert.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_clip_reclassify_raster.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_dashboard.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_datum.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_describe_raster.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_discovery_log.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_duckdb_sandbox.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_engines.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_environment.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_failure_manifest.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_gdal_policy.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_geodesy.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_geoparquet2.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_grid_registration.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_lineage.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_measure_area.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_merge_simplify.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_nearest_explode.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_network.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_overlay_dissolve.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_path_containment.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_preview.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_raster_reproject_bands.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_resample_raster.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_retrieval.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_retrieval_at_scale.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_retrieval_degradation.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_sampling.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_select_and_extract.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_smoke.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_south_up_and_mixed.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_sql_axis_order.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_sql_credentials.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_stacks.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_summaries.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_summarize_points_in_polygons.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_terrain_derivatives.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_tier_a_operations.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_ui.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_verification_status.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_verify_repair.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_voronoi.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_whitebox.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_whitebox_derivatives.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_whitebox_encoding.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_whitebox_geographic_refusal.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_worked_example.py +0 -0
- {mapsmith-0.7.0 → mapsmith-0.8.0}/tests/test_workspace.py +0 -0
|
@@ -33,6 +33,7 @@ data/
|
|
|
33
33
|
tests/data/*
|
|
34
34
|
!tests/data/manifest_spec_validator.py
|
|
35
35
|
!tests/data/manifest-v1.schema.json
|
|
36
|
+
!tests/data/manifest_spec_multi_file_digest.py
|
|
36
37
|
# The discovery benchmark: 95 queries written by a second model from work
|
|
37
38
|
# scenarios, never from this catalog. It is tracked BECAUSE it is a dataset — an
|
|
38
39
|
# evaluation set nobody can inspect is a claim, not a measurement.
|
|
@@ -6,7 +6,198 @@ All notable changes to MapSmith are documented here, in the format of
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
## [0.8.0] - 2026-10-05
|
|
10
|
+
|
|
11
|
+
Records declare `spec_version` `1.0.0-draft.10`. `draft.9` defines the digest
|
|
12
|
+
of a dataset made of several files, which is what the first fix below
|
|
13
|
+
implements; `draft.10` gives each input a role and a configuration of its own.
|
|
14
|
+
**One key moves**: a second input's own georeferencing facts,
|
|
15
|
+
`inputs[].x-mapsmith:environment` on 0.7.x, are now `inputs[].environment`.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`cluster_points_by_distance`: group points chained together within a
|
|
20
|
+
distance.** "Group everything within half a kilometre" is single linkage, not
|
|
21
|
+
thinning: two points share a cluster when a chain of points joins them with
|
|
22
|
+
every link at most the distance long, so chains merge, and every point is kept
|
|
23
|
+
with its `cluster_id` and `cluster_size`. `thin_points` keeps one point per
|
|
24
|
+
crowd, and its count is close to the number of groups but not equal to it. A
|
|
25
|
+
check re-reads the written file and re-derives the chains by a second
|
|
26
|
+
algorithm. A geographic CRS is refused, and so are lines, polygons and
|
|
27
|
+
multipart points, which a point cluster has no single position for.
|
|
28
|
+
- **`list_operations` takes a `geometry`: what the caller's layer holds**, as
|
|
29
|
+
`point`, `line` or `polygon`, or a list of them for a mixed layer. The
|
|
30
|
+
catalogue now has 31 operations taking one vector layer to another, one more
|
|
31
|
+
than the size it hands over whole, so a caller declaring everything it knew
|
|
32
|
+
got a shortlist back for the most common request there is; with the geometry
|
|
33
|
+
declared, the right operation is in the answer for 116 of the 118 benchmark
|
|
34
|
+
requests again. An entry declares the families it accepts only where its code
|
|
35
|
+
refuses the others (fifteen do), an entry without a declaration is kept for
|
|
36
|
+
every family, and a test calls each declared entry with every family it
|
|
37
|
+
leaves out and requires the refusal.
|
|
38
|
+
- **`benchmarks/choice_at_scale.py`**, with every choice it scores in
|
|
39
|
+
`benchmarks/choice_at_scale.json`: each of the 118 requests handed to Claude
|
|
40
|
+
Opus 5.5 in sets of 30, 40 and 50 candidates and compared with itself. From
|
|
41
|
+
40 it chose right 6.9 points less often than from 30, from 50 5.1 points
|
|
42
|
+
less, so the size `list_operations` hands over whole stays 30, and a test
|
|
43
|
+
holds it at or below the largest size measured without loss.
|
|
44
|
+
- **Each input of an operation that reads more than one dataset says which
|
|
45
|
+
argument read it** (`inputs[].argument`, spec `draft.10`). The order of
|
|
46
|
+
`inputs` is not significant, so until now a weighted zonal statistic with
|
|
47
|
+
values and weights swapped produced a record that differed only in file
|
|
48
|
+
names. Eighteen operations name their inputs (twenty code paths:
|
|
49
|
+
`spatial_join` has three engines), with the names the input checks
|
|
50
|
+
already use in `verification[].argument`, so the two can be joined; a
|
|
51
|
+
single-input record is unchanged.
|
|
52
|
+
|
|
53
|
+
- **`get_lineage` takes a `layer`.** The history of one layer of a file
|
|
54
|
+
geodatabase now starts from the geodatabase itself: its record sits beside
|
|
55
|
+
the container (spec section 3.1), and the walk matches the layer at every hop
|
|
56
|
+
into a container. A layer given for a path that is not a container is
|
|
57
|
+
refused rather than ignored.
|
|
58
|
+
|
|
59
|
+
### Changed
|
|
60
|
+
|
|
61
|
+
- **`get_lineage` steps carry `inputs[].argument`, `inputs[].environment` and
|
|
62
|
+
`output.layer`** when the records hold them.
|
|
63
|
+
- **A shapefile output whose member files are ambiguous** (`out.dbf` beside
|
|
64
|
+
`out.DBF`, possible only on a case-sensitive filesystem) is written with a
|
|
65
|
+
manifest that has no `output` digest and a note saying why, instead of a
|
|
66
|
+
digest that would choose one of the two.
|
|
67
|
+
- **`inputs[].x-mapsmith:environment` is `inputs[].environment`**, the field
|
|
68
|
+
spec `draft.10` defines for the configuration that changed how one input was
|
|
69
|
+
read. Same content; only the name moves, and a reader of 0.7.x records reads
|
|
70
|
+
the old one.
|
|
71
|
+
- **With more than one input, no file's facts sit on the record's
|
|
72
|
+
`environment`, the first input's included.** A sidecar beside the first
|
|
73
|
+
raster was recorded there, which the format reads as true of the whole run,
|
|
74
|
+
since the order of `inputs` means nothing: in a `clip_raster` it read as true
|
|
75
|
+
of the mask. It is now on that input's own `environment`, and only process
|
|
76
|
+
settings stay on the record. Seven operations can meet it (clip_raster,
|
|
77
|
+
sample_raster_at_points, elevation_profile, least_cost_path, viewshed,
|
|
78
|
+
watershed, zonal_statistics), and only when a sidecar is present. A
|
|
79
|
+
single-input record is unchanged.
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
|
|
83
|
+
- **`cluster_points_by_distance` is bounded.** The pairs of points within the
|
|
84
|
+
distance grow with the square of the points when the distance is large, and
|
|
85
|
+
6,000 points at a distance of 1e9 took two minutes and 2.3 GB. The pairs are
|
|
86
|
+
now found in blocks and the call is refused once they pass
|
|
87
|
+
`MAPSMITH_MAX_SAMPLES`, the operator's limit, before memory is spent.
|
|
88
|
+
`hot_spots` with `weights="distance_band"` uses the same query: its weights
|
|
89
|
+
were a double loop over every pair of features in Python, quadratic at any
|
|
90
|
+
band, and a band that is not a finite positive number is now refused.
|
|
91
|
+
- **`get_lineage` no longer hashes every shapefile in the workspace.** Since
|
|
92
|
+
the draft.9 digest rule it computed, while indexing, the other rule's digest
|
|
93
|
+
of every shapefile record's output, whatever the chain asked about. A
|
|
94
|
+
shapefile is now hashed only when a hop misses on it.
|
|
95
|
+
- **A Windows junction inside a `.gdb` is not followed.** The digest of a
|
|
96
|
+
directory container walked into junctions, which `is_symlink()` does not
|
|
97
|
+
report, so files outside the container could enter its digest. Section 3.3 of
|
|
98
|
+
the specification says links are neither members nor followed. The size used
|
|
99
|
+
for the re-hash cap now counts the same files as the digest.
|
|
100
|
+
- **A single-layer `.gdb` read by any operation records its layer.** Every
|
|
101
|
+
operation now reads a single-layer file geodatabase, not only
|
|
102
|
+
`extract_layer`, and the input records the layer it read: without it, a
|
|
103
|
+
lineage walk meeting the record of that layer stopped with a false "records
|
|
104
|
+
claim this container's digest, but for other layers".
|
|
105
|
+
- **`get_lineage` refuses a `layer` holding `:` or a control character.** On
|
|
106
|
+
Windows `:` in a file name opens an alternate data stream of another file.
|
|
107
|
+
- **`thin_points` refuses a distance that is not a finite positive number**,
|
|
108
|
+
like `cluster_points_by_distance`; and `least_cost_path` accepts a start or
|
|
109
|
+
end given as a single-part MultiPoint, which it used to count as no point.
|
|
110
|
+
- **`zonal_statistics` refuses zones that are not polygons.** Line zones were
|
|
111
|
+
accepted and returned the line's length in pixels as the `count`, with a
|
|
112
|
+
mean over the cells it crossed: a plausible table, and not a zonal
|
|
113
|
+
statistic. `count_in_polygons` and `summarize_points_in_polygons` refuse a
|
|
114
|
+
polygon layer holding anything else the same way. Found by the test that
|
|
115
|
+
holds the new geometry declarations to the code.
|
|
116
|
+
- **`least_cost_path` is found by a caller holding points.** It reads a cost
|
|
117
|
+
raster and two point layers but declared its input as raster only, so a
|
|
118
|
+
caller who said, correctly, that it held a vector layer never saw it. A start
|
|
119
|
+
or end layer holding a line or a polygon beside its point is now refused; it
|
|
120
|
+
used to pass with the extra feature ignored.
|
|
121
|
+
- **`snap_layer` snaps to a polygon or multipart reference.** Both crashed with
|
|
122
|
+
a raw `NotImplementedError` from shapely; every vertex of every part, holes
|
|
123
|
+
included, is now a target.
|
|
124
|
+
- **A feature with no geometry is refused, with a count, by `thin_points`,
|
|
125
|
+
`sample_raster_at_points`, `points_along_lines` and `line_intersections`.**
|
|
126
|
+
They crashed on it with an `AttributeError`, "cannot convert float NaN to
|
|
127
|
+
integer" and a failed sort.
|
|
128
|
+
- **A shapefile's digest covers its attributes and its CRS.** An input or
|
|
129
|
+
output shapefile was digested as its `.shp` alone, so a record kept matching
|
|
130
|
+
a dataset whose `.dbf` (attributes) or `.prj` (CRS) had been rewritten, while
|
|
131
|
+
it said `sha256` is of the bytes. The digest is now the listing of the
|
|
132
|
+
`.shp`, `.shx`, `.dbf`, `.prj` and `.cpg` files, as section 3.3 of the
|
|
133
|
+
specification defines it since draft.9; a file geodatabase input is digested
|
|
134
|
+
the same way, lock files excluded. `get_lineage` recomputes digests with the
|
|
135
|
+
same function, so a chain written by this release still resolves. A record
|
|
136
|
+
written by an earlier release for a shapefile carries the old digest:
|
|
137
|
+
`get_lineage` compares it under the rule it was written with, so an
|
|
138
|
+
unchanged shapefile is not reported as edited, and a chain that crosses
|
|
139
|
+
from a record written by 0.7.1 to one written now still resolves. A record
|
|
140
|
+
written before this release can only vouch for the `.shp`: an edit to the
|
|
141
|
+
attributes or the CRS made after it was written stays invisible to it, so
|
|
142
|
+
such a hop is reported as `reverified_shp_only`, not `reverified`, and a
|
|
143
|
+
history that contains one is not `verified`.
|
|
144
|
+
- **`extract_layer` works on a file geodatabase.** The catalog listed `.gdb`
|
|
145
|
+
among its containers, and the input digest opened the path as a file: a
|
|
146
|
+
geodatabase is a directory, so the operation died before writing anything.
|
|
147
|
+
- **`get_lineage` matches the layer when a digest belongs to a container.**
|
|
148
|
+
Two records for two layers of one geodatabase share the container's digest
|
|
149
|
+
(spec section 6, draft.9); a hop into a container now resolves to the record
|
|
150
|
+
for the layer it names, and stops with `other_layer` rather than attribute
|
|
151
|
+
one layer to the operation that wrote another. It re-checks a layer's record
|
|
152
|
+
against the container it sits beside (`<container>.<layer>.provenance.json`,
|
|
153
|
+
spec section 3.1), and two layers of one container reached on two branches
|
|
154
|
+
are two histories.
|
|
155
|
+
- **A URI in a manifest path keeps its `//`, so its credentials are masked.**
|
|
156
|
+
The path normaliser collapsed `https://user:pw@host/x` to `https:/user:pw@…`,
|
|
157
|
+
and the redaction of URI credentials looks for `://`: the password would have
|
|
158
|
+
stayed in the record. Not reachable while remote paths are refused, which is
|
|
159
|
+
the default; found in the 0.7.0 audit. The weights raster of
|
|
160
|
+
`zonal_statistics` is recorded through the same normaliser now.
|
|
161
|
+
- **A redaction in the environment of a secondary input is declared.** Masked
|
|
162
|
+
values there did not raise `parameters_redacted`, so the record changed a
|
|
163
|
+
value without saying so.
|
|
164
|
+
- **The package metadata names the Copernicus NOTICE** beside the AGPL licence,
|
|
165
|
+
since the source distribution ships the two Copernicus clips the notebook
|
|
166
|
+
reads.
|
|
167
|
+
- **The links on the PyPI page resolve.** PyPI renders the README with nothing
|
|
168
|
+
to resolve a relative link against, so every link to a document of the repo
|
|
169
|
+
and the map-panel screenshot answered 404 there. The build now rewrites them
|
|
170
|
+
to GitHub URLs pinned to the release tag; on GitHub the README is unchanged.
|
|
171
|
+
|
|
172
|
+
## [0.7.1] - 2026-09-26
|
|
173
|
+
|
|
174
|
+
Two fixes from the pre-release audit of 0.7.0, both present in earlier releases,
|
|
175
|
+
and the Argleton run scored against trap 024's corrected truth, which arrived
|
|
176
|
+
after the 0.7.0 tag: that release's PyPI page still quotes the recount of the
|
|
177
|
+
old run. No answer 0.7.0 got right changes, and no manifest key moves: records
|
|
178
|
+
declare `spec_version` `1.0.0-draft.8`, as in 0.7.0.
|
|
179
|
+
|
|
180
|
+
### Fixed
|
|
181
|
+
|
|
182
|
+
- **An output that is one of the inputs, or has an extension its writer cannot
|
|
183
|
+
honour, is refused before anything is written**
|
|
184
|
+
([#31](https://github.com/mapsmith-ai/MapSmith/issues/31)). `zonal_statistics`
|
|
185
|
+
with `output_path` equal to its weights raster replaced the raster with a
|
|
186
|
+
directory of shapefiles — a vector writer given `.tif` falls back to the
|
|
187
|
+
Shapefile driver — and failed before writing a manifest. The dedicated tools
|
|
188
|
+
now refuse both at the boundary, as `run_operation` already refused the
|
|
189
|
+
collision; and the plan validator turns an extension no writer can honour from
|
|
190
|
+
a warning into an error (`OUTPUT_EXTENSION_REFUSED`), keeping the warning for
|
|
191
|
+
writable but non-canonical ones such as `.geojson`.
|
|
192
|
+
|
|
193
|
+
- **A spacing could ask for any number of points.** `elevation_profile` and
|
|
194
|
+
`points_along_lines` materialised every point before looking at how many
|
|
195
|
+
there were: a spacing of `1e-6` on a 90 m line is ninety million. Both now
|
|
196
|
+
count the points from the line lengths and refuse above
|
|
197
|
+
`MAPSMITH_MAX_SAMPLES` (default 1 000 000), an environment variable of the
|
|
198
|
+
server that no tool argument can move; `SECURITY.md` says what is and is not
|
|
199
|
+
limited. `points_along_lines` also refuses a NaN or infinite spacing, which
|
|
200
|
+
`spacing <= 0` let through.
|
|
10
201
|
|
|
11
202
|
## [0.7.0] - 2026-09-26
|
|
12
203
|
|
|
@@ -12,8 +12,8 @@ identifiers:
|
|
|
12
12
|
value: "10.5281/zenodo.22213091"
|
|
13
13
|
description: "Concept DOI — always resolves to the latest release"
|
|
14
14
|
license: AGPL-3.0-or-later
|
|
15
|
-
version: "0.
|
|
16
|
-
date-released: "2026-
|
|
15
|
+
version: "0.8.0"
|
|
16
|
+
date-released: "2026-10-05"
|
|
17
17
|
keywords:
|
|
18
18
|
- GIS
|
|
19
19
|
- geospatial
|
|
@@ -5,7 +5,7 @@ MapSmith is professional-grade geoprocessing with **verifiable provenance**, rea
|
|
|
5
5
|
## Non-negotiable invariants
|
|
6
6
|
|
|
7
7
|
1. **Deterministic outputs only**: geometry and numbers always come from engine executions, never from a model.
|
|
8
|
-
2. **Provenance on every writer**: any function that writes a dataset emits `<output>.provenance.json` via `ProvenanceRecord` (inputs with sha256, exact parameters, motivated CRS decisions, engine+version).
|
|
8
|
+
2. **Provenance on every writer**: any function that writes a dataset emits `<output>.provenance.json` via `ProvenanceRecord` (inputs with sha256, exact parameters, motivated CRS decisions, engine+version). The digest is `provenance.dataset_sha256` -- for a shapefile or a `.gdb`, the listing digest of spec section 3.3 -- never `sha256_of` on a dataset path, which hashes one file and missed a shapefile's attributes and CRS until 0.7.1.
|
|
9
9
|
3. **Deterministic verification**: the order is the invariant — verify, repair what is mechanically repairable, write the manifest, and *only then* enforce, so the audit trail survives the error instead of being lost with it. `verify.audited(...)` does all four and is how a new writer is written. A minority still open-code the same sequence inline; that is a migration in progress rather than a second sanctioned shape, and two tests hold the line so this sentence does not have to carry a number that rots: one fails if any hand-written writer skips `verify.enforce`, the other ratchets their count downwards only. (It used to say writers go through `audited` "so the invariant cannot be bypassed", which was false for seventeen of fifty-seven — and one of them enforced nothing at all.) Preconditions: `verify.verify_loaded_inputs(...)` (per input: CRS presence is CRITICAL, emptiness is a warning) **before** any CRS alignment, and `verify.verify_input_pairs(...)` (extent overlap) **after** — get that order wrong and a raw pyproj error beats our own message. Never re-read data just to check it. `verify.audit_on_failure(...)` keeps the diagnosis on disk when the engine crashes. Mechanical repair (`make_valid`) is bounded (2 rounds), writes to a temp file and swaps atomically, uses `gdf.geometry.name` (never assumes "geometry"), refuses multi-layer containers and multi-file formats, and lands in `repairs` — in the manifest *and* in the tool result. Anything needing judgement (empty results, CRS choices) is a hinted check in `warnings`, never silently fixed. `on_empty` is `fail` only where emptiness is provably a bug (buffer, reproject), `warn` where it is legitimate but suspicious (clip, join).
|
|
10
10
|
4. **Explicit CRS discipline**: reject inputs without a CRS (helpful message, naming the declaration when the file has one MapSmith will not resolve); never run metric ops on geographic CRS without a recorded reprojection decision. Resolving a CRS and labelling one are different jobs and live apart: `readers.native_crs` returns coordinate systems, `verify.crs_label` returns strings for manifests, and comparisons go through `verify.same_crs` — a label fed back in as a CRS is how a declared CRS got dropped (#28). The keys of `crs_decisions` are the specification's (`analysis_crs`, `source_crs`, `target_crs`, `transformation`, `reason`) or they carry the `x-mapsmith:` prefix that says they are ours — stricter than section 3.5, which only recommends a prefix, and a house rule because a SHOULD with nothing reading it let two operations invent synonyms of `analysis_crs` for a week. `tests/test_verify.py` derives the recommended set from the vendored schema and sweeps every writing site in the source; `tests/test_showcase.py` does the same for every manifest shown on the README or in the gallery, which the source sweep cannot see.
|
|
11
11
|
5. **GPL boundary**: QGIS/GRASS only via subprocess (CLI/files/JSON), never in-process imports.
|