Reconcile catalog API + validate grid redesign: Catalog fetch/shard-map split, odc.geo grids, signature-enforced runs#23
Conversation
891043e to
3198245
Compare
Catalog backend validation — findings so farInvestigated spherely vs mortie for The regression was a stale mortie, not the method
The CLI was also still calling the deprecated EPSG:3031 path (it never used spherely/mortie); fixed to route through the grid path with Backend sweep (vs spherely = 76,575 pairs)
Sweet spot: MOC @ order 8 ≈ 12 s — spherely-equivalent, ~3.5× faster, no extra dependency. Commission shrinks with order and is the safe direction (extra files get read then filtered downstream; cost is just parsing). The 9 residual omissions are REAL, not artifactsGeodesic adjudication (pyproj Pending
|
|
General notes on clean-up / packaging--
Realistically, all of this needs to be addressed before we can validate anything, since we'll be validating the arbitrary grid case as well as morton, and arbitrary won't work without these items being resolved. |
Re (1): integrating the optimized spherely (
|
Phase 0 results
Harness: Baselines captured (all from the identical 4,103-granule cached cycle-22 pull, order-6 fullsphere):
The spherely build reproduced this issue's reference exactly (76,575 pairs), which validates the harness. Drift findings (recorded in
|
Phases 1 & 2 — status
Phase 1 (deps/packaging) — committed; plus the spherely fork is built and working:
Phase 2 (fetch layer) — done:
|
… comments for pole behavior notes
Phase 3 — GridSpec protocol + odc.geo
Full suite: 211 passed (9 new tests). Next: Phase 4 ( |
Phase 4 — ShardMap + backends + spherely wheel
Spherely fork wheel is now installable (resolves the conda-toolchain hassle): the fork's Full suite: 222 passed (11 new). Legacy Deferred followup — Option B: gh-pages find-links index for spherelyThe current Fix (do after Phases 5–7): publish the fork's built wheels (cp310–cp314 + mac/windows, all already on the Needs: a small workflow job that lays built wheels into |
Phase 5 (in progress) — 5a + 5b: single source of truth + signature enforcement
The catalog/run grid-mismatch footgun (a shard map built for one grid silently mis-run against another) is now closed. 5a — single source of truth: 5b — runner rewired:
Full suite: 223 passed. Remaining in Phase 5: 5c (the |
Phase 5 complete — the hard-break (5a–5d)
Net −1,344 / +211 lines ( Behavior preserved (drift check): the new Original request works end-to-end: Full suite: 198 passed. Next: Phase 7 ( |
Phase 7 complete — plan done (Phases 0–7)
Full suite: 201 passed. No stale Plan status
#24 can be closed once this is reviewed. Operational follow-ups (out of plan scope)
|
arm64 Lambda layer/function build + deploy (for Apple Silicon)
What changed (why a rebuild is needed)The #24 catalog refactor changed the Lambda layer:
Reference: the x86_64 layer built clean at 125 MB unzipped / 76 MB zipped (well under the 250 MB limit), so arm64 should be comfortable too. pyproj's PROJ data is the main weight. Build (run on Apple Silicon, arm64-native — podman)Both builds must run inside a linux/arm64 manylinux_2_28 container so every wheel is linux-arm64 (running cd <repo root>
# 1) Layer (numpy is built from source with 64KB page alignment for arm64 — a few min)
podman run --rm -v "$PWD":/workspace:Z -w /workspace/deployment/aws \
quay.io/pypa/manylinux_2_28_aarch64 \
bash -c "yum install -y zip && chmod +x build_layer.sh && ./build_layer.sh arm64"
# -> deployment/layers/lambda_layer_arm64.zip (script enforces <250MB unzipped)
# 2) Function code (needs py3.12 on PATH inside the container)
podman run --rm -v "$PWD":/workspace:Z -w /workspace \
quay.io/pypa/manylinux_2_28_aarch64 \
bash -c "export PATH=/opt/python/cp312-cp312/bin:\$PATH && yum install -y zip && bash deployment/aws/build_function.sh"
# -> deployment/builds/lambda_function_arm64_py312.zipIf the layer is over budget, the strip block in Verify the layer imports (no earthaccess, geo present)podman run --rm -v "$PWD":/workspace:Z quay.io/pypa/manylinux_2_28_aarch64 bash -c '
cd /tmp && unzip -q /workspace/deployment/layers/lambda_layer_arm64.zip
/opt/python/cp312-cp312/bin/python -c "
import sys; sys.path.insert(0, \"python\")
import pyproj, odc.geo, shapely, numpy, pandas, h5coro, mortie
print(\"layer imports OK:\", pyproj.__version__)"'Deploy (CloudFormation; both arches are py3.12 now)
# Uploads layer+function zips to the artifact bucket and creates/updates the stack.
bash deployment/aws/stand_up.sh arm64
# (sets LAYER_ZIP=lambda_layer_arm64.zip, FUNC_ZIP=lambda_function_arm64_py312.zip)Or Gotchas
|
arm64 Lambda build — done (Apple Silicon, native, podman)
Built arm64-native on Apple Silicon (podman machine = Results
Comparable to the x86 reference (125 MB unzipped); the numpy-from-source (64 KB page align) + pyproj/odc-geo additions land us at 128 MB with plenty of headroom. Build commands (ran exactly as in the handoff, podman)# layer
podman run --rm -v "$PWD":/workspace:Z -w /workspace/deployment/aws \
quay.io/pypa/manylinux_2_28_aarch64 \
bash -c "yum install -y zip && chmod +x build_layer.sh && ./build_layer.sh arm64"
# function (cp312 on PATH)
podman run --rm -v "$PWD":/workspace:Z -w /workspace \
quay.io/pypa/manylinux_2_28_aarch64 \
bash -c "export PATH=/opt/python/cp312-cp312/bin:\$PATH && yum install -y zip && bash deployment/aws/build_function.sh"Layer import verificationnumpy resolved to Artifacts
Not yet done (next steps)
Build scripts at HEAD now match the handoff spec (py3.12, pyproj+odc-geo present, no earthaccess). Ready to deploy on your go. |
… comments for pole behavior notes
Status — 2026-06-10 (updated)
This PR's scope grew into the full catalog-API reconcile (#24): a fetch/shard-map
split (
Catalog/CMRSource/ShardMap), anodc.geo.GeoBox-backedRectilinearGrid,grid.signature()enforcement at run time, a--config-driven catalog CLI, the spherelyS2
SpatialIndexbackend (fork wheel hosted as a GitHub release asset), a consolidatedsingle
build_layer.sh(pyproj/odc-geo in, earthaccess out) with x86 bumped to py3.12.Done: the catalog hard-break (Phase 5.4 —
build_catalog+ the EPSG:3031/grid-drivenlegacy paths removed) and the CHANGELOG (5.6). Spherely-vs-mortie equivalence (orig.
Phase 2) was validated via
bench/drift_catalog.py: the newShardMap.buildreproducesthe pre-refactor catalogs 100% (0 omission / 0 commission) for both backends on
cycle-22 ATL06. CI: Lint / Tests (3.12 + 3.13) / Docs green; the Lambda layer builds at
~125 MB unzipped.
Remaining (the soft-deprecation culls + post-merge ops):
process_morton_cell(5.1),xdggs_zarr_template(n_parent_cells=)(5.2), the HEALPixdenselayout (5.3 — biggest,spans 5 files), and the cryocloud notebook ARN (5.5); plus live deploy (Phases 3–4) and
CFN teardown docs (Phase 6). Per the plan, merging first eases CloudFormation testing.
Plan: Validate the grid/catalog redesign, then cull deprecated code
Checklist
Phase 0 — integrate spherely
general_grids(spherely) onmainrelease-cleanup-testingon updatedmainuv sync+.[catalog,analysis,test])Phase 1 — offline validation
python -m pytest -qgreen (202 passed)python -m pytest -m slowgreen (2 passed)ruff check src/ tests/cleanPhase 2 — catalog (spherely vs mortie)
cat_spherely.json+cat_mortie.jsonbuilt--polygon/--bboxspot-checkedPhase 3 — local end-to-end (real ATL06)
./test_healpix.zarr(group12) verified./test_rect.zarr(grouprectilinear) verifiedPhase 4 — deployment + Lambda
stand_up.sh/deploy.sh)Phase 5 — cull deprecated code (gated on Phases 1–4)
process_morton_cellxdggs_zarr_template(n_parent_cells=...)denselayout (default →fullsphere)_build_catalog_healpix/_grid_driven+ shapely dispatch)cryocloud_example.ipynb(ARN +process_shard)CHANGELOG.md[Unreleased]Phase 6 — CFN teardown docs
docs/deployment/lambda.md+README.md)Context
This week's merged PRs reshaped zagg's core: #18 generalized "morton cells" →
arbitrary grids/shards (
process_shard,OutputGridprotocol,HealpixGridRectilinearGrid), Spherical geometry backend and dispatch for build_catalog #19 added a spherely (S2) catalog backend withmortie fallback, and Enable user side infrastructure standup #22 added user-side standup. Several deprecated paths
were left in place deliberately, to be removed after the replacements are
verified. The goal here is a runbook you execute that (1) validates
deployment, the new grid class (rectilinear / non-morton), the still-working
morton workflow, and the new catalog module — combining steps where possible —
and then (2) culls the deprecated code once validation is green.
Key state facts (verified this session)
origin/general_grids, NOT onmain. Currentcatalog.pyon your branch is mortie-only. Per your decision we landspherely on
mainfirst, then rebase the working branch on it — so the"new catalog module" (spherely+mortie) is part of
mainand its legacy pathsbecome cull-eligible on a clean base.
HealpixGridstill defaults tolayout="dense"(src/zagg/grids/healpix.py:48)while
from_configdefaults tofullsphere(src/zagg/grids/__init__.py:42)— an inconsistency to fix in the dense cull.
process-morton-cell→process-shardfunction-name rename is staged(uncommitted) on
release-cleanup-testing.pytest(testpaths=tests, only markerslow,pytest-covpresent).plus the cryocloud notebook; CFN teardown = docs only.
Phase 0 — Land spherely on
main, then rebase the working branchmain: landorigin/general_gridsontomain(preferred: open a PR
general_grids→mainand merge it; or mergelocally) and push
main(you've authorized pushing to main). Resolve anymerge conflicts — likely confined to
src/zagg/catalog.pyand friends, sincegeneral_gridsis the branch that changed them.process-shardrename onrelease-cleanup-testing.release-cleanup-testingonto the updatedorigin/main(nowcontaining spherely). Expect conflicts where the rename meets the redesign
edits — resolve favoring the rename + spherely:
src/zagg/{catalog,processing,runner,__init__,schema,config}.py,src/zagg/grids/*,deployment/aws/lambda_handler.py,pyproject.toml,tests/*.uv syncthenuv pip install -e '.[catalog,analysis,test]'(the
catalogextra bringsspherely).# TODO(cull #NN): remove after validationcomments at each deprecated definition (process_morton_cell,dense branches,
xdggs_zarr_template(n_parent_cells=...),_build_catalog_healpix/_build_catalog_grid_driven/shapely dispatch + legacyparent_order) and atop-cell note in
notebooks/cryocloud_example.ipynb, so the cull targets areself-documenting before removal.
Phase 1 — Offline automated validation (no network)
This proves the deprecated paths still work and their replacements are
equivalent — the gate for culling.
Confirm specifically:
tests/test_integration.py::test_dense_fullsphere_equivalencepasses (dense removal is safe).tests/test_rectilinear.py+ the rectilinear case intest_integration.pypass (non-morton grid emits/writes correctly).tests/test_catalog.pypasses incl. the spherely path now present from general_grids.test_grids.py::test_healpix_explicit_dense_warns,test_runner.py::test_dense_layout_emits_warning.Phase 2 — Catalog module: spherely vs mortie (needs network: CMR)
Tests the new catalog and produces the catalogs reused downstream (combined
per your note).
shard_keys+ per-shard granule coverage between the two (reuse theexisting
bench/verify_spherely_cycle22.py; target the ~100% shard /<0.01% pair disagreement already reported in Spherical geometry backend and dispatch for build_catalog #19).
--polygon <geojson>and--bboxpaths produce a valid catalog.Phase 3 — Local end-to-end: morton + rectilinear (needs NASA Earthdata)
Validates
process_shardagainst real ATL06 reads, both grid types.12vsrectilinear),array shapes, populated cells, and aggregation variables match config.
--driver https) to validate s3→httpsURL rewriting from catalog metadata.
Phase 4 — Deployment + Lambda backend (needs AWS creds; small $ cost)
gb_seconds/estimated_cost_usd.aws cloudformation delete-stack --stack-name zagg-backend(after emptying the staged zips + bucket — see Phase 6).
Phase 5 — Cull deprecated code (gated on Phases 1–4 green)
One focused commit per item; re-run Phase 1 after each. Inventory with
exact anchors:
process_morton_cell— remove defsrc/zagg/processing.py:432-472;drop from
src/zagg/__init__.py:29,52; updatedocs/index.md:72,80,docs/api/processing.md:7,docs/design/{architecture.md:117,schema.md:64};fix
tests/test_lambda_build.py:39import +tests/conftest.py:17docstring.xdggs_zarr_template(n_parent_cells=...)— remove the param/branchsrc/zagg/schema.py:43-97(warn at :82); updatetests/test_schema.py(e.g.
test_array_shape_n_parent_cells) andtests/test_grids.py:239-247.denselayout — change default tofullsphere(
src/zagg/grids/healpix.py:48); remove dense branches inhealpix.py,grids/__init__.py:34-41,config.py:154-157,386,runner.py:30-39,105,259-269,schema.py; updatedeployment/aws/lambda_handler.py:87-98(dropn_parent_cells/dense setup); retire dense tests + the now-trivialtest_dense_fullsphere_equivalence; updatetest_grids.py::test_default_layout_is_dense._build_catalog_healpix+_build_catalog_grid_driven+geometry_backend in {"shapely","shapely-3031"}dispatch + the legacy
parent_order=kwarg path insrc/zagg/catalog.py(keep spherely + mortie); update
tests/test_catalog.pyaccordingly.notebooks/cryocloud_example.ipynb: update thehardcoded ARN
...:function:process-morton-cell→process-shardand switchany
process_morton_cellusage toprocess_shard.CHANGELOG.md[Unreleased]with the removals.Phase 6 — CFN teardown docs (docs only)
Add a Teardown section to
docs/deployment/lambda.md(and a line inREADME.mdStep 2) documenting: (a) staged layer/function zips uploaded bystand_up.share not removed bydelete-stack—aws s3 rmthem; (b) astack-created output bucket must be emptied before
delete-stacksucceeds; then(c)
aws cloudformation delete-stack --stack-name zagg-backend.Critical files
src/zagg/grids/{base,healpix,rectilinear,__init__}.pysrc/zagg/catalog.py(spherely arrives via general_grids merge)src/zagg/processing.py,src/zagg/runner.py,src/zagg/__main__.pysrc/zagg/schema.py,src/zagg/config.pysrc/zagg/configs/{atl06.yaml,atl06_polar.yaml}deployment/aws/{template.yaml,stand_up.sh,deploy.sh,lambda_handler.py,invoke_lambda.py}tests/test_{grids,rectilinear,catalog,processing,runner,integration,schema,lambda_build}.py,tests/conftest.pynotebooks/cryocloud_example.ipynbbench/verify_spherely_cycle22.pyVerification (how we know it worked)
pytest+pytest -m slow+ruff) before any cull, andre-green after each cull commit.
./test_healpix.zarr(group12) and./test_rect.zarr(grouprectilinear) open and contain expected shapes/vars from real reads.git grep -nE "process_morton_cell|layout.*dense|n_parent_cells|_build_catalog_healpix|_build_catalog_grid_driven|shapely-3031"returns only intended/none; noDeprecationWarningleft for removed items.Out of scope — track as follow-up issues (from PR loose ends)
H3Grid (Phase 1 design),
odc.geo.GeoBoxforRectilinearGrid,zagg.fast_read(),spherely aarch64 wheel, PolygonZoneGrid/S2Grid (Phases 2/3), backend refactor
(#20), Python-API status (#13), temporal (#12). Not part of this validate+cull pass.