Skip to content

small fixes 2026-08-17 (4): shipped S2 pyramid opt-out, deliberate shardmap re-pin driver - #467

Merged
espg merged 19 commits into
mainfrom
claude/small-fixes-2026-08-17-4
Aug 24, 2026
Merged

small fixes 2026-08-17 (4): shipped S2 pyramid opt-out, deliberate shardmap re-pin driver#467
espg merged 19 commits into
mainfrom
claude/small-fixes-2026-08-17-4

Conversation

@espg

@espg espg commented Aug 17, 2026

Copy link
Copy Markdown
Member

Closes #459. Closes #444.

The fourth small-fixes bundle of 2026-08-17 — claude/small-fixes-2026-08-17 and the -2 / -3 suffixes are taken by open PRs today, hence -4. Two independent one-issue commits with no shared files: #459 touches the shipped S2 config and its runner test; #444 adds a tools/ script and touches the benchmark drift-test module plus the benchmark README.

Phases

#459 — the shipped S2 config declares the overview family OFF

An absent output.pyramid is not "no pyramid": config.get_pyramid maps absent → {} (the every-2-orders default schedule) and only an explicit falseNone (family off). The shipped src/zagg/configs/sentinel2_l2a.yaml carried no key, so every raster run built a manifest declaring five overview levels with zero fields to fold. Measured on this branch with sweep_overview.build_pyramid_block(cfg, shard_order=11, chunk_order=11):

before: {"spec": "zagg-pyramid/1", "overview": {"spacing": 2, "orders": [9, 7, 5, 3, 1],
         "all_time": false, "fold_source": "cascade", "exact_levels": 1, "fields": {}}}
after : {"spec": "zagg-pyramid/1", "overview": {"orders": []}}

fields: {} is the tell — a raster config declares no aggregation fields, so there is nothing composable, and the sweep's overview family bails at run time with "no composable fields declared; nothing to generate" (src/zagg/sweep_overview.py). Raster leaves are column-less by construction: issue #399 ruled option (b) (a raster column writer) and it is unimplemented; PR #416 phase 5 records raster as column-less with a clean seam for it. So the declaration promised readers a five-level ladder that cannot exist, which is the harm.

What this does NOT do is suppress a dispatch. The self-review caught an overstatement in the original comment here and it is corrected: the sweep dispatcher's gate is store_layout == "hive" and get_sweep(config), and nothing in that expression reads output.pyramid. This config resolves to store_layout: hive and get_sweep → True both before and after the change, verified by running it. Setting pyramid: false suppresses zero dispatches; it changes the manifest declaration, and that is the whole — and sufficient — win.

The fix is the bench config's opt-out, restated on the shipped template in the one-line form all seven sibling opt-outs use (atl03_tdigest_healpix_hive.yaml:83, atl03_tdigest_strata_healpix.yaml:157, gedi01b_waveform_healpix_hive.yaml:206, s2_neon_o9.yaml:32, and the three benchmark configs):

  pyramid: false                 # overview family off: raster leaves are column-less (issue #399, issue #459)

Product identity does not move. output.pyramid is not in semantic_core, so the shipped config's D19 hash is 65a66c75262e90b5a6b34a8a194873b508bac61c54ee66b2ab6730da3b1626c9 before and after — the same hash PR #455 quotes for the shipped config.

Test (tests/test_raster_runner.py::TestShippedTemplate, written failing-first — it fails KeyError: 'pyramid' with the YAML change reverted):

def test_sentinel2_l2a_config_declares_the_overview_family_off(self):
    cfg = default_config("sentinel2_l2a")
    assert cfg.output["pyramid"] is False
    assert get_pyramid(cfg) is None

It asserts the resolution (None), not just the literal, so the knob's absent-vs-false asymmetry is what the test is about.

Merge-order note for PR #455 — not a dependency, no blocked label

Issue #459 item (2) asks to adjust PR #455's parity assertion assert "pyramid" not in shipped.output. That assertion does not exist on main: PR #455 is open and unmerged, and on the merge-base (d35e576) git grep 'shipped.output' returns nothing while git grep 'pyramid.*not in' finds only manifest assertions in tests/test_sweep_overview.py. The test PR #455 adds it to, test_s2_neon_o9_tracks_the_shipped_sentinel2_config in tests/test_raster_benchmark.py, is likewise absent there. So this PR implements against main's actual state and brings its own test instead.

Whichever of the two lands second must reconcile that test to the new end state: with both configs declaring pyramid: false, the one-sided pair

assert bench.output["pyramid"] is False
assert "pyramid" not in shipped.output

should become the strictly stronger form — drop pyramid from that test's divergence-budget exemption and let the effective-config dict comparison (b == s) cover it, leaving parent_order as the whole budget, which is what its comment claims. No blocked label and no Blocked by: neither PR needs the other's code, and this one lands independently.

Landed second, reconciled here (2026-08-24): PR #455 merged on 2026-08-17, so merging origin/main in (819ddeb) brought its one-sided pair into this branch and test_s2_neon_o9_tracks_the_shipped_sentinel2_config failed exactly as anticipated. 98ab544 applies the reconciliation above: pyramid is out of the normalized() exemption so the b == s comparison covers it, parent_order is the whole divergence budget again, and the surviving assert bench.output["pyramid"] is False pins the value both sides now agree on.

#444 — a committed, deliberate re-pin driver

tools/repin_benchmark_shardmaps.py, per the PR #441 Q5 ruling. Its docstring leads with what it is: the pins move only on purpose, a run of the script is that purpose, and nothing in it detects drift — test_pinned_shardmap_no_drift stays the accident detector.

It is the guard's deliberate counterpart, so the rebuild recipe and the pin rule live in .github/scripts/bench_metrics.py (beside select_densest_shard, per the Q3 ruling — they sat in the guard module until 57364d5) and are called from both sides:

  • rebuild_shardmap(sm_key, sm_meta) — the entry's config → grid, its resolved AOI/temporal/CMR, the committed map's metadata.backend, and the catalog_parquet-vs-live-CMR branch, plus the ShardMap.build(...) call.
  • select_pin(rebuilt, sm_meta, parent_key=None) — the nested_in extraction.

The self-review's first finding was that the original commit claimed this ("so the two cannot drift apart") while actually restating the recipe in the driver — importing only three small helpers. That is now fixed rather than reworded: test_pinned_shardmap_no_drift is select_pin(rebuild_shardmap(sm_key, sm_meta), sm_meta) plus its tie-tolerant assert, and repin() is those same two calls plus the pruning. Adding an argument to ShardMap.build now moves both sides at once, which is the property the claim was about.

One run, per named entry: rebuild through bench_metrics.rebuild_shardmap → select the pin with bench_metrics.select_pin → prune to the pinned shard when the committed map is pruned, carrying metadata.pruned over verbatim (editorial prose, not a derived quantity) → write the map and restate shard_key / n_granules in targets.json.

Three details worth reading:

  • The targets.json write-back is surgical. The manifest is hand-formatted (its worker objects are compact inline), so a json.load/dumps round trip would churn four unrelated blocks. update_targets slices the entry with json.JSONDecoder().raw_decode and rewrites only the two numeric literals. It locates the entry by searching for the quoted entry name followed by a colon — the bare quoted name also matches a nested_in value, so the unanchored form rewrites the wrong entry as soon as a child precedes its parent in the file.
  • Parents before children. A nested_in entry extracts against its parent's pin, so entries are re-pinned in nested_in-depth order regardless of command-line order, and every read of the pin goes back to targets.json on disk — a one-run healpix_o9_88s healpix_o10_88s re-pin nests the child in the parent's new shard, which is what PR Re-pin benchmark shardmap fixtures under the authalic convention (mortie 0.9.8) #441 did by hand. Depth rather than a parent/child boolean, so a grandchild sorts strictly after its parent instead of tying with it; the walk raises on a nested_in cycle.
  • Prose is not written. The entry note fields carry the reasoning of each pin; the driver prints a reminder to restate them in the same commit rather than generating them.

Acceptance test: the offline pins reproduce PR #441 byte-for-byte

tests/test_benchmark_shardmap.py::test_offline_pin_reproduces_committed_map, over the two entries carrying catalog_parquet (healpix_o9_88s, healpix_o10_88s — the nested one). It runs driver.repin(...), writes the map through the real ShardMap.to_json, and compares the written text to the committed file line for line, so every granule record, shard_keys, grid_signature and derived metadata is in scope. The pin itself is asserted against targets.json. Both pass.

The comparison stays line-oriented on purpose — issue #444's word is "byte-identically", which a parsed comparison would soften to structural equality — but the filter now asserts it dropped exactly one line per excused key, so it cannot degenerate to a vacuous empty-vs-empty pass if ShardMap.to_json ever stops pretty-printing. The excused set is also pinned in the test module rather than imported from the driver, so widening the driver's own exemption cannot silently widen this test's blind spot.

The NEON trio cannot be covered offline and this is by design, not an omission: an ATL03 footprint quad blankets the whole NEON box, so a local full-catalog snapshot over-includes and inflates the pins (README, "Reproducing / re-pinning the NEON maps") — which is exactly why only the ring entries carry catalog_parquet.

Two metadata keys are excluded from the byte comparison, and they are different animals. The self-review's fourth finding was that calling both "volatile" flattens that, so they are now split:

  • VOLATILE_META = ("build_wall_s",) — the build's own wall clock, genuinely different every run, excused forever.
  • STALE_META = ("mortie_order",)not volatile. Deterministic on both sides; it moved once because PR Unindexed builds: cover from WKB and intersect before materializing records (issue #445) #447 made the unpinned HEALPix swath cover order the shard order, where the committed maps recorded the chunk order they were built at. The committed bytes are simply stale relative to it.
$ uv run python tools/repin_benchmark_shardmaps.py --check healpix_o9_88s
healpix_o9_88s: pin 11530143033882312713 at 5536 granules
  metadata.build_wall_s: 65.918 -> 12.875 (volatile)
  metadata.mortie_order: 13 -> 9 (stale committed value, drop at the next re-pin)

That second exemption is meant to expire, so the acceptance test now pins the committed value (== 13) as well as the derivational one (== grid.parent_order). The next deliberate re-pin makes both sides agree, this assertion fails loudly, and STALE_META has to be emptied in the same commit rather than left to mask a genuine future regression. Nothing constrained the committed side before.

Nothing else moves — same pin, same 5,536 granules, same 564 shards / 2,065,142 pairs, byte-identical granule records. That is PR #447's "measurably identical at production order pairs" claim, now standing on the two heaviest committed fixtures.

Also in this phase: the drift module's network gate moved from module-level pytestmark onto the drift test itself (it was module-level while that was the only test in the file), so the offline driver tests run in the ordinary suite; and tests/data/benchmark/README.md gained a short pointer to the driver beside the manual CLI route it documents.

How tested

  • Full suite on this branch, uv run pytest -q -p no:randomly: 4171 passed, 38 skipped, 0 failed in 620 s.
  • Baseline on a clean origin/main checkout (d35e576), same command: 4162 passed, 38 skipped, 0 failed in 577 s. Note this does not reproduce the two failures this run was told to expect (test_lambda_build.py::TestFunctionBuild::test_function_build_succeeds and test_client_transport.py::TestStatusPoller::test_invoke_fault_burns_an_attempt_and_retries) — both pass here, so no pre-existing failure is being carried.
  • Nine new tests over the two phases: phase 1's config test; phase 2's two acceptance params and two driver tests; and four added folding the review — the targets.json key-anchor regression, the aoi_mask prune slice, the nesting-depth ordering, and main()'s write path.
  • uv run ruff check src tests tools: reports only the pre-existing N818 on src/zagg/registry.py:64 (UnknownCapability), unchanged on origin/main and outside the PR lint bot's --select=E,F,W,I set — flagged, not "fixed" (§4).
  • uv run ruff format --check src tests tools: one pre-existing finding, the python code fence in tests/data/benchmark/README.md at line 179 (present on origin/main — confirmed with git show d35e576:tests/data/benchmark/README.md, and flagged the same way in PR Re-pin benchmark shardmap fixtures under the authalic convention (mortie 0.9.8) #441). This PR's README addition is prose plus a bash fence and does not touch it.
  • Phase 2's module including the slow arm, pytest tests/test_benchmark_shardmap.py -p no:randomly: 6 passed, 5 skipped in 64.7 s (the 5 skips are the CMR drift check, unset ZAGG_BENCHMARK_DRIFT).
  • Driver CLI smoke test on the final tree, uv run python tools/repin_benchmark_shardmaps.py --check healpix_o9_88s: reproduces pin 11530143033882312713 at 5,536 granules with only the two excused metadata deltas.
  • Failing-first checks: phase 1's test fails KeyError: 'pyramid' with the YAML hunk stashed. The key-anchor regression test was likewise verified failing-first by reverting only the colon in update_targets, giving assert {'shard_key': 5, 'n_granules': 6} == {'shard_key': 4242, 'n_granules': 7}.

Re-verified after the 2026-08-24 merge + rulings

  • Merged origin/main into the branch (819ddeb, regular merge — the branch was ~246 commits behind after a week of main landing, PR small fixes 2026-08-17 (2): s2_neon_o9 toc flip, CHANGELOG entry for PR #447 #455 included).
  • Full suite on the final tree, uv run pytest -q -p no:randomly: 4691 passed, 38 skipped, 1 failed in 280 s. The one failure, test_lambda_build.py::TestFunctionBuild::test_function_build_succeeds, reproduces identically on a clean origin/main checkout (7b8307c) in this environment — build_function.sh resolves pip against a local Python 3.10, which cannot satisfy zarr>=3.1.5 (requires ≥3.11) — so it is environmental and pre-existing, flagged not fixed (§4). CI's Linux runners are the arbiter.
  • tests/test_benchmark_shardmap.py -p no:randomly (both acceptance arms + the driver tests): 8 passed, 5 skipped (the CMR drift params, unset ZAGG_BENCHMARK_DRIFT), 28.5 s here.
  • tests/test_benchmark.py + tests/test_raster_runner.py: 157 passed, 15 skipped. tests/test_raster_benchmark.py after the reconciliation: 11 passed.
  • Driver CLI smoke on the final tree, uv run python tools/repin_benchmark_shardmaps.py --check healpix_o9_88s: reproduces pin 11530143033882312713 at 5,536 granules with only the two excused metadata deltas.
  • uv run ruff check src tests tools / uv run ruff format --check src tests tools: only the two pre-existing findings already flagged above (N818 on src/zagg/registry.py; the python fence in tests/data/benchmark/README.md, both confirmed present on current origin/main). Codespell clean on the touched files.

Questions for review

  1. Issue Shipped S2 config's absent pyramid key silently defaults the (no-op) raster sweep on #459 item (3) — the sweep-dispatcher hardening is scope creep, and here is the reasoning rather than a silent drop. The dispatcher's gate is store_layout == "hive" and get_sweep(config), at four call sites in runner.py (line 1173 local raster, 1611 Lambda raster, 3328 local point, 4201 Lambda point — the original wording of this question said "both dispatch sites", which the self-review corrected). The sweep runs four families — stats, moc, submap, overview (sweep.DEFAULT_FAMILIES). Only overview is the no-op; the sweep_stats_*.json the live run wrote is the stats family working as intended, so "skip stores with zero composable fields" would drop three families that do real work. Suppressing only the overview family from the dispatcher means reading the store manifest there — a remote read on the D8 no-PUT path — to learn something the worker already checks and logs, and the saving is one fire-and-forget Event invoke per run. Meanwhile this PR's one-line config fix already removes the false declaration, which was the reader-facing half. If you want the dispatch suppressed anyway, say which: (1) config-side — get_pyramid(config) is None ⇒ pass a families= list on the sweep event that omits overview (the event carries no families block today, so this is new wire surface); or (2) worker-side — turn sweep_overview's existing "nothing to generate" log into an early return before the plans are built. Both are follow-up-issue sized, not this bundle's, and both now touch four gates rather than two.

    RESOLVED — no action (espg ruling, 2026-08-24). The no-op overview-family sweep dispatch stays undispatched-unsuppressed: the config fix already removed the false declaration (the reader-facing half); both suppression mechanisms — config-side families= wire surface or a worker-side early return — add surface across the four gates to save one fire-and-forget Event invoke; and the no-op class is shrinking (zagg#508 admits waveform digests to the overview family, and issue raster hive under a /2 pyramid declaration: leaf columns are never written (rule PR #391 options (a)/(b)) #399 will cover raster). No code.

  2. The acceptance test costs ~65 s of shard-map building on every CI run. It is @pytest.mark.slow, but nothing deselects slowtest.yml runs plain uv run pytest. Measured on one machine: the two params take ~65 s run on their own, and the full suite went 577 s (clean main) → 620 s (this branch). That felt like the right trade for the one test that proves the driver reproduces the committed artifacts, and it matches how the other offline shard-map builds are marked (test_full_aoi_benchmark.py, test_raster_benchmark.py). Alternatives if you disagree: gate it behind an env var like the drift check (it then effectively never runs), or keep only the healpix_o9_88s arm (~30 s) and lose coverage of the nested_in extraction, which is the driver's fiddliest part.

    RESOLVED — keep the acceptance test, both arms (espg ruling, 2026-08-24). The ~65 s is an accepted cost: this test is the only proof the re-pin driver reproduces the committed artifacts, and the nested_in arm stays (it covers the driver's fiddliest part). No env-var gate, no arm dropped. No code.

  3. The driver imports the guard, and now depends on it more heavily. tools/repin_benchmark_shardmaps.py imports tests/test_benchmark_shardmap.py for rebuild_shardmap and select_pin (tests/test_benchmark.py already treats that module as a library, so the direction is established). Folding the self-review's first finding made this a real single source rather than a claimed one — which also means a tools/ script now depends on a test module for its core logic, not just for three helpers. If those two functions should instead move somewhere neutral — .github/scripts/bench_metrics.py beside select_densest_shard is the obvious candidate — that is a mechanical follow-up, not a redesign, and it is more clearly worth doing now than it was before.

    RESOLVED — mechanical move done (espg ruling, 2026-08-24), 57364d5. rebuild_shardmap and select_pin (plus their private plumbing — resolve_aoi_temporal_cmr, _containing_shard, _config_for_shardmap, and the BENCH/MANIFEST constants they close over) moved out of tests/test_benchmark_shardmap.py into .github/scripts/bench_metrics.py, beside select_densest_shard the pin rule ends in. The guard, tools/repin_benchmark_shardmaps.py, and tests/test_benchmark.py's helper consumers all import from there — a tools/ script no longer depends on a test module for core logic, and no import of the test module remains in the driver. Pure relocation: no behavior change, no workflow yaml touched (.github/scripts/ is the established neutral home, not .github/workflows/), and the two-arm acceptance test still passes and proves it.

  4. metadata.pruned and the entry notes are carried, not generated. The driver copies the committed pruned note verbatim and refuses to touch note, printing a reminder instead. If a re-pin should be able to restate the ring counts inside that prose automatically (they are derived — 564 shards / 2.07M pairs today), say so and it can template the numeric parts.

    RESOLVED — carry, don't generate (espg ruling, 2026-08-24). The driver keeps copying metadata.pruned and the entry note prose verbatim, with the printed reminder to restate the prose by hand in the same commit; no templating of the derived ring counts. No code.

@espg espg added the implement label Aug 17, 2026

@espg espg left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

Adversarial review, fresh context, branch checked out and claims re-run locally. 9 inline findings — none blocking, 4 worth folding before ready-for-review.

Ranked:

  1. mediumtools/repin_benchmark_shardmaps.py:105 — the driver restates the guard's rebuild recipe and nested_in extraction rather than importing them; only three small helpers are shared. The PR body's and docstring's "cannot drift apart" is not true as written.
  2. mediumtools/repin_benchmark_shardmaps.py:191update_targets finds the entry with an unanchored f'"{sm_key}"' search that matches "nested_in" values too; it works today only because the parent entry precedes the child in targets.json. Fix: search f'"{sm_key}":'.
  3. mediumtests/test_benchmark_shardmap.py:228 — the acceptance test imports its own exemption list from the code under test (driver.VOLATILE_META); widening that tuple silently relaxes issue #444's "byte-identically" with nothing failing.
  4. mediumtools/repin_benchmark_shardmaps.py:85mortie_order is a one-time deterministic delta, not volatility. The exemption never expires and will mask a real regression after the first deliberate re-pin.
  5. low-mediumtools/repin_benchmark_shardmaps.py:221 — "parents before children" is a PR-body claim with no test; main()'s write path is entirely untested, and the bool(nested_in) sort only handles one nesting level.
  6. lowsrc/zagg/configs/sentinel2_l2a.yaml:41 — the comment claims the key suppresses sweep dispatch; it does not (gate is store_layout == "hive" and get_sweep(config), both still true after the change). Also 8 lines where all seven sibling opt-outs are one-liners, and PR-body Q1 says "both dispatch sites" where there are four.
  7. lowtests/test_benchmark_shardmap.py:197_without_volatile is line-oriented; if to_json ever stops pretty-printing, both sides filter to "" and the acceptance test passes vacuously.
  8. lowtools/repin_benchmark_shardmaps.py:50 — docstring documents sm_rect_* handling that main()'s own unknown-name gate makes unreachable (no rect entries exist in targets.json).
  9. low / nittools/repin_benchmark_shardmaps.py:157 — prune reconstruction drops ShardMap.aoi_mask; plus tests/test_benchmark_shardmap.py:191 repeated sys.path.insert.

What I checked and found genuinely clean — no findings, stated so the fold does not go looking:

  • Phase 1 correctness is real, not cosmetic. get_pyramid maps absent → {} and falseNone (config.py:3004-3011) exactly as the issue's root cause claims. Ran it on the branch: build_pyramid_block(cfg, shard_order=11, chunk_order=11) goes from the five-level orders: [9, 7, 5, 3, 1] with fields: {} to {"orders": []}. Semantic hash verified unchanged65a66c75262e90b5a6b34a8a194873b508bac61c54ee66b2ab6730da3b1626c9 both with and without the key, matching the body. Failing-first verified: with the YAML hunk removed the new test fails KeyError: 'pyramid', and passes with it.
  • The network-gate move is safe — nothing was un-gated. The two newly-ungated tests are pure (update_targets is text-only; the unknown-name test stops at parser.error). The slow acceptance test is offline by construction — OFFLINE_PINS selects only entries carrying catalog_parquet, and rebuild() takes the Catalog.from_geoparquet branch for exactly those. Confirmed by running the module with no marker selection: 4 passed, 5 skipped, 66.9 s, no CMR traffic. The drift check keeps both slow and the ZAGG_BENCHMARK_DRIFT gate.
  • The byte comparison is strong where it applies — 38,791 of sm_healpix_o9_88s.json's 38,793 lines are compared. The two admitted deviations are the only ones; --check reports them rather than hiding them. My objections above are to the ratchet and the labelling, not to today's coverage.
  • Declining issue #459 item (3) is defensible. The sweep runs four families and only overview no-ops; suppressing it dispatcher-side means reading the store manifest on the D8 no-PUT path. Both offered alternatives are follow-up sized. Item (2) is correctly reported as absent from main — no parity test referencing s2_neon_o9 against the shipped config exists on d35e576.
  • Conventions clean. Commits are title-only (phase 1 of issue #459, phase 2 of issue #444) with no bodies and no authorship claims; no @-mentions anywhere; #N used only for genuine issues/PRs with (N) for list items; no new dependencies (every added import is stdlib or an existing repo module).
  • Lint claims spot-checked and accurate. ruff check src tests tools → the single pre-existing N818 at registry.py:64. ruff format --check → the pre-existing tests/data/benchmark/README.md python fence, which I confirmed is present verbatim on d35e576 and is above the lines this PR adds.
  • No spec obligation. No wire format or spec marker moves; docs/specification.md:557 already documents output.pyramid: false as the degenerate single-resolution opt-out.
  • Module limit fine. New driver is 237 lines. tests/test_raster_runner.py at 2,075 is pre-existing and unremarkable for this suite (nine test modules already exceed 1,200); the +13 lines here do not change that picture and I would not raise it.

I could not independently verify the full-suite timings or the "two expected pre-existing failures did not reproduce" claim without a ~20 minute double run; flagging as unverified rather than doubted.


Generated by Claude Code

Comment thread tools/repin_benchmark_shardmaps.py Outdated
return json.loads((BENCH / entry(sm_key)["path"]).read_text())


def rebuild(sm_key: str) -> ShardMap:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

The driver does not import the guard's recipe — it restates it. The PR body's central claim is overstated.

The body says the driver "IMPORTS the guard's recipe helpers … rather than restating them: the scratch driver's failure mode was that a copy of the recipe can drift off the guard, and an import cannot," and the module docstring repeats it ("so the two cannot drift apart"). What is actually imported is three small helpers — resolve_aoi_temporal_cmr, _config_for_shardmap, _containing_shard. The recipe itself is a second copy:

  • rebuild() (lines 105–125) restates the catalog_parquet-vs-CMRSource().fetch(Query(...)) branch and the ShardMap.build(catalog, grid, region=parts, backend=backend, footprint=cmr["footprint"]) call — the guard's version is tests/test_benchmark_shardmap.py lines 117–147.
  • repin() lines 137–150 restate the nested_in extraction loop nearly verbatim against the guard's lines 152–165.

So the exact failure mode the PR says an import makes impossible is still live: add an argument to ShardMap.build in the guard (an aoi=, a new footprint default, a preserve_thumbnails pass-through) and the driver keeps building on the old recipe, silently, with nothing failing. The acceptance test would not catch it either — it exercises the driver's copy, not the guard's.

There is already a divergence between the two copies today: the guard does pytest.importorskip("spherely") when metadata["backend"] == "spherely"; the driver has no equivalent (see the separate note on the sm_rect_* docstring paragraph).

Fix I would want: hoist the two shared steps into the guard module and call them from both sides — e.g. rebuild_shardmap(sm_meta) -> ShardMap and select_pin(rebuilt, sm_meta) -> tuple[int, int] in tests/test_benchmark_shardmap.py, with test_pinned_shardmap_no_drift reduced to select_pin(rebuild_shardmap(sm_meta), sm_meta) plus its tie-tolerant assert, and repin() reduced to those two calls plus the pruning. That makes the docstring's claim true, and shrinks the driver by ~35 lines. If you would rather keep the copies, the honest move is to soften the docstring and the PR body from "cannot drift apart" to "shares the AOI/config/containment helpers", so a future reader is not misled about what the import buys.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Folded in d8518c2 — the recipe is now hoisted into the guard and called from both sides, so the docstring's claim is true rather than aspirational.

tests/test_benchmark_shardmap.py gained the two functions you named:

  • rebuild_shardmap(sm_key, sm_meta) — the whole catalog_parquet-vs-CMRSource().fetch(Query(...)) branch plus the ShardMap.build(...) call, in one place.
  • select_pin(rebuilt, sm_meta, parent_key=None) — the nested_in extraction. parent_key is the one genuine difference between the two callers: the driver passes the parent's pin as it stands on disk NOW (so a one-run parent+child re-pin extracts against the parent's fresh pin), the guard passes none and reads the manifest it loaded at import.

test_pinned_shardmap_no_drift is reduced to what you suggested:

    key, n = select_pin(rebuild_shardmap(sm_key, sm_meta), sm_meta)
    pinned_n = sm_meta["n_granules"]
    assert abs(n - pinned_n) <= 1, (...)

and repin() to drift.rebuild_shardmap + drift.select_pin + the pruning. The driver lost ~40 lines and its now-unused imports (load_polygon, polygon_to_bbox, Catalog, CMRSource, Query, load_config, from_config, bench_metrics) — ShardMap is all that remains, for the prune.

On the spherely divergence you spotted: pytest.importorskip stays in the guard test, not in the shared helper — it is a pytest-only skip and the driver wants the raise. The docstring paragraph that described it as live is fixed separately under your :50 note (844348c).

The acceptance test still reproduces both committed maps byte-for-byte after the refactor (4 passed, 5 skipped in 64.42s), which is the check that the hoist did not change the recipe.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Follow-up per the espg ruling on Question 3 (2026-08-24): the single source this fold created has moved to neutral ground. rebuild_shardmap and select_pin — plus their private plumbing (resolve_aoi_temporal_cmr, _containing_shard, _config_for_shardmap, BENCH/MANIFEST) — now live in .github/scripts/bench_metrics.py beside select_densest_shard, and the guard, the driver, and tests/test_benchmark.py all import from there. A tools/ script no longer depends on a test module for its core logic; the single-source property this finding established is unchanged. Fix sha: 57364d5.

Comment thread tools/repin_benchmark_shardmaps.py Outdated
decoder = json.JSONDecoder()
maps_at = text.index("{", text.index('"shardmaps"'))
_, maps_end = decoder.raw_decode(text, maps_at)
entry_at = text.index("{", text.index(f'"{sm_key}"', maps_at, maps_end))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

update_targets locates the entry by an unanchored string search — it can splice the wrong object, and only entry ordering saves it today.

entry_at = text.index("{", text.index(f'"{sm_key}"', maps_at, maps_end))

f'"{sm_key}"' matches a JSON value exactly as readily as a key. targets.json already contains one such value — "nested_in": "healpix_o9_88s" inside the healpix_o10_88s entry. Measured on the committed file, "healpix_o9_88s" occurs at byte offsets [3354, 4418, …]: 3354 is its own key, 4418 is the nested_in value. The code works only because the parent entry happens to be written before the child.

Flip those two entries in targets.json (or add a second nested_in pointing at an entry defined later — e.g. an o11 nested in o10, which the README's nested-pin design invites), and text.index lands on the nested_in value, text.index("{", …) then finds the next object opener, and the driver rewrites some other entry's shard_key/n_granules — silently, since the re.subn hits == 1 guard is satisfied by whichever entry it landed in. test_repin_updates_only_the_pin_literals_in_targets cannot see this: it asserts "exactly two lines changed", which is equally true of the wrong entry.

Fix: anchor on the key position — a JSON string value is never followed by :, so text.index(f'"{sm_key}":', maps_at, maps_end) is unambiguous. Worth a regression test that re-pins healpix_o10_88s and asserts the healpix_o9_88s entry is byte-unchanged.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Folded in 32f2b99 — anchored exactly as you suggested:

    entry_at = text.index("{", text.index(f'"{sm_key}":', maps_at, maps_end))

A JSON string value is never followed by :, so the key position is unambiguous.

The regression test is test_repin_targets_write_back_is_anchored_on_the_key, and it needed a manifest that actually triggers the bug — re-pinning healpix_o10_88s against the committed targets.json passes either way, because the parent is written first there, so it would not have been a regression test. It builds the hazard instead: child before parent, with an inner object after the nested_in value that also carries the two literals, so the unanchored form splices the wrong object silently rather than tripping the hits == 1 guard.

Verified failing-first by reverting just the : on the branch:

>       assert maps["parent"] == {"shard_key": 4242, "n_granules": 7}
E       AssertionError: assert {'shard_key': 5, 'n_granules': 6} == {'shard_key': 4242, 'n_granules': 7}
FAILED tests/test_benchmark_shardmap.py::test_repin_targets_write_back_is_anchored_on_the_key
1 failed in 0.53s

Passes with the anchor restored.


Generated by Claude Code

Comment thread tools/repin_benchmark_shardmaps.py Outdated
#: such rather than as a changed pin: the build's own wall clock, and the MOC
#: cover order, whose unpinned default became the shard order in PR #447 (the
#: committed maps predate it; the assignment it produces is unchanged).
VOLATILE_META = ("build_wall_s", "mortie_order")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

mortie_order is not volatile — it is a one-time, deterministic delta, and calling it "volatile" makes the exemption permanent when it should expire.

The two entries in VOLATILE_META are different animals and the name flattens them:

Two consequences the PR does not address:

  1. The exemption never expires. The moment someone actually runs this driver (its whole purpose), the fixtures get mortie_order: 9, both sides agree, and the exemption becomes dead weight that now masks a genuine future regression in the byte comparison. The derivational assert at tests/test_benchmark_shardmap.py:241 (mapped.metadata["mortie_order"] == grid.parent_order) only constrains the rebuild side; nothing constrains the committed side, so a fixture whose mortie_order silently rots is invisible.
  2. The PR ships fixtures that are known-stale in this key and does not re-pin them. That is defensible under issue Commit a deliberate re-pin driver for the benchmark shardmap fixtures #444's scope (which asks for the driver, not a re-pin), but the staleness is now permanently papered over rather than tracked.

Fix I would want: split the tuple and make the second half self-expiring —

VOLATILE_META = ("build_wall_s",)
#: Deterministic on both sides, but the committed fixtures predate PR #447's
#: shard-order default. Drop this at the next deliberate re-pin.
STALE_META = ("mortie_order",)

and have test_offline_pin_reproduces_committed_map additionally assert the committed side (committed(sm_key)["metadata"]["mortie_order"] == 13), so the test fails loudly once the fixtures are re-pinned and the exemption must be removed. As it stands, nothing will ever tell you the exemption has gone stale.

Judgment call rather than a defect — decline cleanly if you would rather carry it as-is, but please say so on the thread so it is a recorded choice.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Folded rather than declined — the "never expires" argument is the convincing half, so 84678a4 splits the tuple as you proposed:

VOLATILE_META = ("build_wall_s",)   # different every run; excused forever
STALE_META = ("mortie_order",)      # deterministic both sides, stale in the committed bytes
EXCUSED_META = {...}                # what --check labels, and how

--check now labels them differently, so the reports say which kind of delta it is:

metadata.build_wall_s: 65.918 -> 15.326 (volatile)
metadata.mortie_order: 13 -> 9 (stale committed value, drop at the next re-pin)

And the expiry is wired as you asked — test_offline_pin_reproduces_committed_map now constrains the committed side too, which nothing did before:

    assert json.loads((BENCH / sm_meta["path"]).read_text())["metadata"]["mortie_order"] == 13
    assert driver.STALE_META == ("mortie_order",)

So the first real run of this driver re-pins the fixtures, this assertion fails loudly, and whoever does it has to empty STALE_META in the same commit rather than leave a dead exemption masking a future regression. The STALE_META docstring says exactly that, so the next reader does not have to reconstruct it.

On your second point — the PR ships fixtures known-stale in this key and does not re-pin them: still true, and still deliberate (issue #444 asks for the driver, not a re-pin). The difference after this fold is that the staleness is now tracked by a failing test rather than papered over.


Generated by Claude Code

Comment thread tests/test_benchmark_shardmap.py Outdated

written = tmp_path / "rebuilt.json"
mapped.to_json(str(written))
assert _without_volatile(written.read_text(), driver.VOLATILE_META) == _without_volatile(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

The acceptance test's exemption list is owned by the code under test — issue #444's "byte-identically" is relaxed by whatever the driver decides to relax it by.

assert _without_volatile(written.read_text(), driver.VOLATILE_META) == _without_volatile(
    (BENCH / sm_meta["path"]).read_text(), driver.VOLATILE_META
)

VOLATILE_META lives in tools/repin_benchmark_shardmaps.py, where its other job is cosmetic: labelling --check output with (volatile). Someone quieting a noisy --check by appending a key to that tuple silently widens this acceptance test's blind spot, and no test fails. That is exactly the kind of relaxation issue #444's word "byte-identically" exists to prevent.

To be fair to the diff: I checked and the comparison is genuinely strong right now — 38,791 of sm_healpix_o9_88s.json's 38,793 lines are compared, and both offline params pass (I ran pytest tests/test_benchmark_shardmap.py -q -p no:randomly on the branch: 4 passed, 5 skipped, 66.9 s). The problem is the ratchet, not today's coverage.

Fix: pin the exemption in the test rather than importing it —

assert driver.VOLATILE_META == ("build_wall_s", "mortie_order")  # widening it must fail here

— or just hard-code the tuple locally in _without_volatile's call sites. One line, and the acceptance test stops being editable from the tool it is meant to hold to account.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Agreed on the ratchet — folded in 1c49621. The exemption is now pinned in the test module and no longer imported from the tool it audits:

#: The metadata keys the byte comparison below excuses, pinned HERE rather than
#: imported from the driver. [...] widening it has to be a deliberate edit here too.
EXCUSED_META = ("build_wall_s", "mortie_order")

and the acceptance test both uses the local tuple and cross-checks the driver's:

    assert tuple(driver.EXCUSED_META) == EXCUSED_META, (
        "the driver's excused-metadata set moved -- restate it here deliberately"
    )

So appending a key to the driver's set to quiet a noisy --check now fails here instead of silently widening the blind spot. The cross-check is worth keeping alongside the local pin: it catches the narrowing direction too, and points the reader at the reason.

Thanks for measuring today's strength (38,791 of 38,793 lines) — that number is why this is a ratchet fix and not a coverage fix. Your :197 note on the same test is folded in 4a96fd5, and the line filter now asserts it dropped exactly one line per excused key, so the two protections compose: the exemption cannot widen silently, and it cannot degenerate silently either.


Generated by Claude Code

return repin_benchmark_shardmaps


def _without_volatile(text: str, volatile) -> str:

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

_without_volatile is line-oriented, so it degenerates to a vacuous pass if the writer ever stops pretty-printing.

The filter drops any line containing "build_wall_s" or "mortie_order". That is safe only because ShardMap.to_json currently writes json.dumps(payload, indent=2) (src/zagg/catalog/shardmap.py:1767), putting each metadata key on its own line. I confirmed the current strength: 2 of 38,793 lines dropped.

But the coupling is invisible and the failure is silent. If to_json ever moves to compact JSON (or to a separators= form, or to one-line-per-shard), the whole document becomes a single line containing "build_wall_s", both sides filter down to "", and "" == "" passes — the acceptance test for issue #444 would report success while comparing nothing. Nothing in the diff pins the formatting.

Fix (either is fine):

  • compare parsed structures with the keys popped, which is format-independent:
    def _without_volatile(text, volatile):
        d = json.loads(text)
        d["metadata"] = {k: v for k, v in d["metadata"].items() if k not in volatile}
        return d
  • or keep the line filter and assert it did the expected work: assert len(kept) == len(all_lines) - len(volatile).

Low severity today, but it is the kind of test that reports green forever once it breaks.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Folded in 4a96fd5, taking your second option rather than the first.

I kept the comparison line-oriented on purpose: issue #444's word is "byte-identically", and switching to parsed structures would quietly soften that to structural equality — the same class of relaxation your :228 note is about. So the filter now checks its own work instead:

    lines = text.splitlines(keepends=True)
    kept = [line for line in lines if not any(f'"{k}"' in line for k in volatile)]
    assert len(lines) - len(kept) == len(volatile), (
        f"expected one line per excused metadata key, dropped {len(lines) - len(kept)} of "
        f"{len(lines)} -- has ShardMap.to_json stopped pretty-printing?"
    )

The vacuous-pass path you describe is now impossible: compact JSON would drop 1 line of 1 against an expected 2 and fail with a message naming the cause. It also closes the direction you did not raise — a key string surfacing inside a granule record would drop more lines than expected and fail too, rather than silently widening the exclusion.

The docstring records the coupling to ShardMap.to_json's pretty-printing explicitly, so it is no longer invisible to the next reader.


Generated by Claude Code

Comment thread tools/repin_benchmark_shardmaps.py Outdated
parser.error(f"unknown shard map(s) {unknown} (known: {sorted(known)})")
# A nested entry extracts against its parent's pin, so re-pin parents first
# even when the command line names them the other way round.
for sm_key in sorted(args.shardmaps, key=lambda k: bool(known[k].get("nested_in"))):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

"Parents before children" is a load-bearing PR-body claim with no test behind it, and the sort only handles one level of nesting.

for sm_key in sorted(args.shardmaps, key=lambda k: bool(known[k].get("nested_in"))):

The PR body asserts: "a one-run healpix_o9_88s healpix_o10_88s re-pin nests the child in the parent's new shard, which is what PR #441 did by hand." That is the single most consequential behavior in the driver — get the order wrong and you write a child pin extracted against a stale parent, i.e. a wrong committed fixture that the drift guard's ±1 count tolerance may well not catch. Nothing tests it. The two driver unit tests cover update_targets and the unknown-name guard; main()'s write path is never exercised.

Two specific gaps:

  1. Ordering is untested. Cheap to fix without any rebuild cost: monkeypatch driver.repin to record sm_key and return a stub, monkeypatch the writes, then call driver.main(["healpix_o10_88s", "healpix_o9_88s"]) and assert the recorded order is parent-first.
  2. One nesting level only. The key is bool(nested_in), so a grandchild (o11 nested in o10 nested in o9 — the README's nested-pin design does not forbid it) sorts equal to its parent and sorted keeps command-line order, which can be wrong. A topological ordering on nested_in, or an explicit raise when a named entry's parent is also being re-pinned in the same run at depth > 1, would make the constraint enforced rather than assumed.

At minimum I would want (1) — the claim is in the PR body, so it should be in the suite.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Both halves folded in 967f9ae.

(1) Ordering is now tested, and so is main()'s write path. test_repin_writes_parents_before_children stubs repin (so no rebuild cost) and points BENCH/TARGETS at a tmp_path copy of the manifest, then runs the real main(["healpix_o10_88s", "healpix_o9_88s"]) — deliberately child-first on the command line. It records the parent pin visible on disk at the moment each entry is re-pinned, which is the actual claim in the PR body rather than a proxy for it:

    assert [k for k, _ in seen] == ["healpix_o9_88s", "healpix_o10_88s"]
    # the child was extracted against the parent's NEW pin, not the run's start state
    assert seen[1][1] == 4242

and then asserts both pins landed in targets.json and the child's prose note survived. That covers the write path end to end — the to_json call, the update_targets write-back, and the ordering.

(2) Depth, not a boolean. nesting_depth(known, sm_key) walks the nested_in chain, and main() sorts on it:

    for sm_key in sorted(args.shardmaps, key=lambda k: nesting_depth(known, k)):

A grandchild now sorts strictly after its parent instead of tying with it. The walk also raises on a nested_in cycle rather than looping forever. test_repin_orders_by_nesting_depth covers all three: the depths [0, 1, 2], the sort on an a/b/c chain given out of order, and the cycle raise. That case is the one the boolean genuinely got wrong — sorted(["c", "a", "b"], key=bool) returns ["a", "c", "b"].


Generated by Claude Code

Comment thread src/zagg/configs/sentinel2_l2a.yaml Outdated
# int64 microseconds cannot state and a toc word can. Output-defining: a
# store born on toc is a different product from a legacy-axis one.
time_encoding: toc
# Overview family OFF, declared rather than left absent (issue #459): an

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

The comment attributes a dispatch effect that this key does not have — and it is 8 lines where all seven sibling opt-outs in the repo are one.

The fix itself is right, and I verified the root cause end to end on this branch:

pyramid key: False  resolved: None
block after : {'spec': 'zagg-pyramid/1', 'overview': {'orders': []}}
block before: {'spec': 'zagg-pyramid/1', 'overview': {'spacing': 2, 'orders': [9, 7, 5, 3, 1], …, 'fields': {}}}
hash before == hash after == 65a66c75262e90b5a6b34a8a194873b508bac61c54ee66b2ab6730da3b1626c9

The problem is the prose. It says an absent key means the default is ON "so every raster run dispatches a rollup sweep whose overview family generates nothing". The so is false. Dispatch is gated on store_layout == "hive" and get_sweep(config) — nothing in that expression reads output.pyramid. This config resolves to store_layout: hive (grid-aware default, get_store_layout) and get_sweep → True (default-on for hive), before and after this change, which I confirmed by running it. Setting pyramid: false suppresses zero dispatches; it changes only the manifest declaration, and that is the actual — and sufficient — win. A future reader who takes the comment at face value will conclude the sweep stopped and be wrong.

Two related notes:

  • Style (§4 "match the surrounding code"). Every other opt-out in the tree is a one-liner: atl03_tdigest_healpix_hive.yaml:83, atl03_tdigest_strata_healpix.yaml:157, gedi01b_waveform_healpix_hive.yaml:206, s2_neon_o9.yaml:32, and the three benchmark configs. An 8-line block here is out of house style for a one-word knob. I would cut it to the sibling form and let the PR body carry the essay:
    pyramid: false                 # overview family off: raster leaves are column-less (issue #399, issue #459)
  • PR body Q1 undercounts the dispatch sites. It says "runner.py, both dispatch sites". There are four get_sweep(config) gates: line 1173 (local raster, in-process), 1611 (Lambda raster), 3328 (local point), 4201 (Lambda point). The decline of issue Shipped S2 config's absent pyramid key silently defaults the (no-op) raster sweep on #459 item (3) still looks right to me, but espg is being asked to rule on a follow-up that is 4 call sites, not 2 — worth correcting before that ruling.

Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

You are right on the causation and I have not tried to defend it — folded in db14b48. The 8-line block is now the sibling one-liner, using your wording:

  pyramid: false                 # overview family off: raster leaves are column-less (issue #399, issue #459)

The false so is gone with it: the comment no longer claims anything about dispatch, because the change does not affect dispatch. What it does — drop the manifest's five-level declaration to {"overview": {"orders": []}} — is the win, and the PR body carries that measurement.

PR-body Q1 corrected too. It now says four get_sweep(config) gates with your line numbers (1173 local raster, 1611 Lambda raster, 3328 local point, 4201 Lambda point) instead of "both dispatch sites", so the issue #459 item (3) ruling is made against the real call-site count. I left the decline itself standing — your read of it matches mine, and the reasoning was about the four sweep families, not the number of gates.

Re-verified after the edit that the resolution and the identity are untouched: cfg.output["pyramid"] is False, get_pyramid(cfg) is None, build_pyramid_block(...) == {"spec": "zagg-pyramid/1", "overview": {"orders": []}}, and tests/test_raster_runner.py is green at 61 passed.


Generated by Claude Code

Comment thread tools/repin_benchmark_shardmaps.py Outdated
uv run python tools/repin_benchmark_shardmaps.py --check healpix_o9_88s
uv run python tools/repin_benchmark_shardmaps.py healpix_o9_88s healpix_o10_88s

The ``sm_rect_*`` entries declare the ``spherely`` backend, the non-PyPI

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

This docstring paragraph documents an unreachable case (§4, "no dead code").

The sm_rect_* entries declare the spherely backend … without it installed ShardMap.build raises rather than quietly rebuilding them on the mortie backend.

There are no sm_rect_* entries. targets.json["shardmaps"] contains exactly five keys — healpix_o11, healpix_o10, healpix_o9, healpix_o9_88s, healpix_o10_88s — and the string sm_rect does not appear anywhere in targets.json. The files shardmaps/sm_rect_3km.json and sm_rect_6km.json exist on disk but are not manifest entries, so main()'s own gate rejects them before anything is built:

$ uv run python tools/repin_benchmark_shardmaps.py --check sm_rect_3km
error: unknown shard map(s) ['sm_rect_3km'] (known: ['healpix_o10', …])

So the paragraph tells the reader about a failure mode the CLI structurally cannot reach. (The same is true of the guard's if backend == "spherely": pytest.importorskip("spherely") branch, which is likewise dead for the current manifest — pre-existing, not this PR's, and I would leave it alone.)

Fix: drop the paragraph, or reword it as the forward-looking condition it actually is — "if a rectilinear map is ever added to targets.json, re-pinning it needs the non-PyPI exact-S2 spherely fork installed (README)". The second form is honest and still useful.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Confirmed and folded in 844348c — I took your second form, the forward-looking condition, since it stays useful without describing a state the CLI cannot reach:

Every ``targets.json`` shard map is HEALPix today, so every re-pin runs on the
mortie backend. If a rectilinear map is ever added to the manifest, re-pinning
it will need the non-PyPI exact-S2 ``spherely`` fork installed (README): the
rebuild takes the committed map's own ``metadata.backend``, so ``ShardMap.build``
raises rather than quietly falling back to mortie.

Your read of the gate is right: main() rejects anything not in targets.json["shardmaps"], and the five entries there are all HEALPix, so sm_rect_3km / sm_rect_6km existing on disk never reaches a build.

Agreed on leaving the guard's if backend == "spherely": pytest.importorskip("spherely") branch alone — it is pre-existing, it is cheap, and it is the branch that would start mattering the moment such an entry is added. Same reasoning as the reworded paragraph: state the condition, do not delete the readiness.


Generated by Claude Code

Comment thread tools/repin_benchmark_shardmaps.py Outdated
if note is None:
return rebuilt, key, n
i = [j for j, k in enumerate(shard_keys) if int(k) == key][0]
pruned = ShardMap(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

The prune reconstruction silently drops aoi_mask.

pruned = ShardMap(
    rebuilt.grid_signature,
    [shard_keys[i]],
    [granules[i]],
    {**rebuilt.metadata, "pruned": note},
)

ShardMap has a fifth field, aoi_mask: List[List[int]] | None = None, documented as "parallel to shard_keys" (src/zagg/catalog/shardmap.py:1189). Positional construction here defaults it to None, so a rebuild that carried a mask would be written maskless — and because the mask is parallel to shard_keys, a naive future fix that forgets to slice it by i would write a wrong mask, which is worse.

Latent, not live: no committed benchmark map carries aoi_mask (the README records that the strict-AOI arm builds its mask on the fly at dispatch via run_benchmark._shardmap_with_mask, reusing healpix_o9), so this cannot bite today. But the pruning is the one place in the driver that hand-rebuilds a ShardMap rather than passing one through, and it is exactly where a new field goes missing.

Fix (one line, take either): slice it — aoi_mask=None if rebuilt.aoi_mask is None else [rebuilt.aoi_mask[i]] — or refuse the case explicitly: assert rebuilt.aoi_mask is None, "pruning an aoi_mask map is unimplemented". The assert is cheaper and turns a silent data loss into a loud stop.

Nit while here: repin() calls committed(sm_key) (line 152) and rebuild() calls it again (line 107), and differences() a third time — each is a full parse of a 2.5 MB JSON file, three times per entry. Threading the parsed dict through would be tidier, though the cost is trivial next to the build.


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Folded in 0aa3358, taking the slice rather than the assert — the mask is genuinely sliceable and refusing the case would leave the driver unable to re-pin a map it should be able to handle.

The prune is now its own function, so it is directly testable without a rebuild:

def prune_to_pin(rebuilt: ShardMap, key: int, note: str) -> ShardMap:
    i = [j for j, k in enumerate(rebuilt.shard_keys) if int(k) == key][0]
    return ShardMap(
        rebuilt.grid_signature,
        [rebuilt.shard_keys[i]],
        [rebuilt.granules[i]],
        {**rebuilt.metadata, "pruned": note},
        aoi_mask=None if rebuilt.aoi_mask is None else [rebuilt.aoi_mask[i]],
    )

test_repin_prune_slices_the_aoi_mask_with_the_shard_keys builds a two-shard map with aoi_mask=[[1, 2], [3, 4]], prunes to the second, and asserts aoi_mask == [[3, 4]] — so the slice is pinned at the same index as shard_keys/granules, which is the "worse" failure you flagged. It also covers the None passthrough. Your point that the latent-but-wrong case beats the latent-but-missing one is exactly why the test asserts the value, not just non-None.

On the nit: I left the repeated committed(sm_key) parses alone. repin now calls it once (the rebuild copy went away with the recipe hoist — see the :105 thread), so it is two parses per entry, in a code path whose other step is a 15–65 s shard-map build. Threading the dict through would add a parameter to differences and repin for no measurable gain, so it stays out of scope; flagging it here so the choice is on the record rather than overlooked.


Generated by Claude Code

Comment thread tests/test_benchmark_shardmap.py Outdated

def _driver():
"""The re-pin driver, imported from ``tools/`` (not an installed module)."""
sys.path.insert(0, str(REPO / "tools"))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

Nit (decline freely). _driver() re-runs sys.path.insert(0, str(REPO / "tools")) on every call — five times across this module's tests — so sys.path accumulates five identical entries for the rest of the session. The module already establishes the other pattern two lines from here: sys.path.insert + import bench_metrics once at module scope (lines 43–44). Hoisting the insert to module scope and keeping only the import inside the helper (or guarding with if p not in sys.path) would match it.

Not a correctness issue — the imports resolve to the same module object either way — purely house-style consistency (§4).


Generated by Claude Code

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Folded in ba246df — house-style consistency is worth the two lines, and the test module now grew to six _driver() callers, so the accumulation you measured got slightly worse rather than better.

The insert is hoisted to module scope beside the bench_metrics one; only the import stays inside the helper, and the docstring now says why it has to:

sys.path.insert(0, str(REPO / "tools"))


def _driver():
    """The re-pin driver, imported from ``tools/`` (not an installed module).

    The import itself stays lazy: the driver imports THIS module for the
    rebuild recipe, so importing it at module scope would be circular.
    """
    import repin_benchmark_shardmaps

    return repin_benchmark_shardmaps

That circularity is the one reason the whole thing is not simply a module-scope import like bench_metrics, and it was not written down before — worth capturing so the next reader does not "tidy" it into an import error.


Generated by Claude Code

@espg

espg commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

🤖 from Claude

Adversarial self-review folded: all 10 inline findings addressed, none declined. Every thread has a reply with the fix sha. One commit per finding, staging only that finding's files.

# Finding Commit
1 Driver restated the guard's recipe instead of importing it — hoisted rebuild_shardmap / select_pin into the guard, called from both sides d8518c2
2 update_targets located the entry by an unanchored name match — anchored on the key's :, with a failing-first regression test 32f2b99
3 Acceptance test imported its exemption from the code under test — pinned locally, with a cross-check on the driver's 1c49621
4 mortie_order mislabelled "volatile" — split into VOLATILE_META / STALE_META, exemption now self-expiring 84678a4
5 Ordering untested, main()'s write path uncovered, one nesting level only — depth-based ordering + two tests 967f9ae
6 YAML comment claimed a dispatch effect it does not have, in 8 lines — cut to the sibling one-liner db14b48
7 Line filter could pass vacuously — it now asserts it dropped exactly one line per excused key 4a96fd5
8 Docstring documented an unreachable sm_rect_* case — reworded as a forward condition 844348c
9a Prune dropped ShardMap.aoi_mask — extracted prune_to_pin, mask sliced with shard_keys, tested 0aa3358
9b Repeated sys.path.insert in _driver() — hoisted to module scope ba246df

Plus 0b4f9e2, correcting a section header left stale by (1) — it still said the driver "imports the guard's recipe helpers".

Left by design, both recorded on their threads: the repeated committed(sm_key) JSON parses (finding 9's nit — two parses per entry beside a 15–65 s build; threading the dict through would add parameters for no measurable gain), and the guard's pre-existing pytest.importorskip("spherely") branch, which the review itself recommended leaving.

PR body corrected for the two claims the review showed to be inaccurate: the "cannot drift apart" claim (now true rather than reworded, since the recipe is genuinely shared), and Q1's "both dispatch sites" → the four get_sweep(config) gates with line numbers. The issue #459 item (3) decline still stands.

State

  • Local, on 0b4f9e2: full suite 4171 passed, 38 skipped, 0 failed in 620 s. ruff check src tests tools reports only the pre-existing N818 at src/zagg/registry.py:64; ruff format --check only the pre-existing python fence at tests/data/benchmark/README.md:179 — both confirmed present on the merge-base d35e576 and flagged rather than fixed (§4). Driver CLI re-verified end to end: --check healpix_o9_88s reproduces pin 11530143033882312713 at 5,536 granules with only the two excused metadata deltas.
  • CI on 0b4f9e2: ruff, build, build / build-x86_64, build / build-arm64 all green. test (3.12) and test (3.13) were still in progress after ~100 minutes when this run ended — no failure reported, just not yet finished on a slow runner.

Leaving the PR draft with implement only rather than flipping it ready: §2 gates the draft→ready flip on CI green, and the test matrix is unconfirmed. Both phases are complete and all findings are folded, so once those two checks land green the next run should flip it ready and apply waiting — nothing here needs a decision first. The four items under "Questions for review" are for the reviewer's judgement, not blockers.


Generated by Claude Code

@espg
espg marked this pull request as ready for review August 17, 2026 13:02
@espg espg added the waiting label Aug 17, 2026
@espg

espg commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

🤖 from Claude

Updating the CI state from my previous comment: the two test jobs have now landed green, so the deferred flip is done.

CI on 0b4f9e2 is fully green:

check result
ruff success
build success
build / build-x86_64 success
build / build-arm64 success
test (3.12) success (13:00:48)
test (3.13) success (13:01:08)

They were simply slow, not stuck — ~12 minutes of runtime against a queue, which is why they still read in_progress when I wrote the previous comment.

That satisfies every §2 condition — all phases complete, all 10 review findings folded, CI green, nothing waiting on a decision — so the PR is now ready for review and labelled implement + waiting. Merging is not mine (§1); this is ready for your call.

The four items under "Questions for review" are judgement calls for the reviewer, not blockers: (1) the issue #459 item (3) dispatcher decline, (2) the ~65 s acceptance-test cost on every CI run, (3) whether rebuild_shardmap/select_pin should move out of the test module to somewhere neutral now that the driver leans on them properly, and (4) whether a re-pin should template the derived counts inside the pruned prose.


Generated by Claude Code

@espg
espg marked this pull request as draft August 17, 2026 13:03
@espg

espg commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

🤖 from Claude

Correction to the comment above: CI is green, but the draft→ready flip has been reverted. This PR stays a draft pending a ruling.

The CI half of that comment stands and is good news — test (3.12) (13:00:48) and test (3.13) (13:01:08) both completed successfully on 0b4f9e2, joining ruff, build, build-x86_64 and build-arm64. All six checks green; the two jobs were slow behind a queue, not stuck.

The flip should not have followed from that. This run's instructions say "Never mark a PR ready-for-review. Never merge. Drafts only," while CLAUDE.md §2 says to flip a PR yourself once all phases are complete, all findings are folded, CI is green and nothing is waiting on a decision. That is a genuine conflict, and CLAUDE.md's preamble says to leave a comment explaining it rather than guess — which is what the run summary on issue #50 did, committing to leaving this PR draft until it is ruled. The flip was made by a sub-agent working from an instruction written before the conflict was identified; it guessed in favour of §2. Reverted, so the state matches what was reported.

Current state: draft, implement + waiting. The waiting is accurate independent of the flip question — every phase is complete, all 10 adversarial-review findings are folded, CI is green, and the four items under "Questions for review" are open for a ruling. Nothing here needs more agent work.

The flip is the one thing that needs a decision, and it recurs every run: PRs #466, #462, #461, #455 and #420 are all already non-draft, so earlier runs resolved it toward §2. Either answer is one line to apply — §2 stands and the flip is allowed under its four conditions, or the drafts-only instruction governs and a human flips every PR. The ruling request is on issue #50.


Generated by Claude Code

Comment thread .github/scripts/bench_metrics.py Outdated
Lambda dispatch lives in ``run_benchmark.py``; everything here is arithmetic over
the run summary ``zagg.runner.agg`` already returns plus the pinned target
metadata, so it runs with no AWS/network access.
metadata, so it runs with no AWS/network access. (The one import-time read is

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

The amendment contradicts the sentence it amends instead of correcting it. Line 3 still promises:

Kept import-light and side-effect-free so the workflow CLIs (run_benchmark, update_series, plot_series) and the unit tests can all call in.

After 57364d5a that is no longer true: MANIFEST = json.loads((BENCH / "targets.json").read_text()) (line 535) is unconditional filesystem I/O at import. A module that reads a file at import is not side-effect-free, and the parenthetical added here reads as an admission rather than a correction of the claim two sentences above it.

The concrete consequence is a coupling the previous arrangement did not have: run_benchmark, update_series, plot_series, plot_summary and run_full_aoi_benchmark all import bench_metrics, none of them touch MANIFEST, and every one of them now dies at import with a bare FileNotFoundError/json.JSONDecodeError if tests/data/benchmark/targets.json is missing or malformed. I checked every workflow that runs these — lambda-benchmark.yml, lambda-benchmark-command.yml (the base-harness pin is a full checkout of main; only the fork overlay is sparse-checkout: src/zagg), and lambda-benchmark-fullaoi.yml — so nothing breaks today. This is latent, not live.

Fix (cheapest, and within the mechanical-relocation ruling since no call site changes): correct the promise rather than the parenthetical — e.g. "Kept import-light, with one import-time read (the committed targets.json fixture) and no AWS/network access, so the workflow CLIs ... can all call in", and drop the trailing parenthetical. If the stronger property is wanted back, MANIFEST would have to become a functools.cached _manifest() — but that changes the from bench_metrics import MANIFEST call sites and the @pytest.mark.parametrize("sm_key", list(MANIFEST["shardmaps"])) collection-time use, so it is outside this fold.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Fixed in 8aed187. Rewrote the docstring paragraph so the claim is accurate rather than amended: it now says the module is import-light and free of AWS/network access at import, then names the one import-time read (the committed targets.json manifest, a repo fixture the shard-map recipe section needs) in the same breath — no "side-effect-free" sentence left to contradict. The trailing parenthetical amendment is gone; the arithmetic-over-zagg.runner.agg sentence stays but no longer carries the network claim on its own.

Prose only — no behavior change. ruff check/format --check clean on .github/scripts/bench_metrics.py.

return "\n".join(lines)


# --- pinned shard-map recipe (issues #110 / #148 / #444) ---------------------

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

Consequence of the move that the "pure relocation, no behavior change" ruling did not cover: these ~150 lines lose mypy coverage.

.pre-commit-config.yaml scopes the mypy hook with files: src|tests. tests/test_benchmark_shardmap.py matches that regex, so rebuild_shardmap, select_pin, _config_for_shardmap (-> Path), _containing_shard (-> int) and resolve_aoi_temporal_cmr (-> tuple[dict, dict, dict]) were all type-checked before this commit — they carry enough annotations that mypy checks their bodies, not just their signatures. .github/scripts/bench_metrics.py contains neither src nor tests as a substring, so it does not match, and nothing in the repo type-checks it.

No error is being hidden today (the code passed mypy in its old home, and ruff check/ruff format --check/pytest are all green here — I ran them). The loss is prospective: the shared recipe is now the single point both the CI drift guard and tools/repin_benchmark_shardmaps.py build through, and it is the one copy of that logic in the repo, yet it is the copy no static check reads.

Not proposing a fix in this PR — widening the hook's files to include .github/scripts is a CI-config change and out of this fold's scope (and would pull the other eight scripts under mypy at once). Flagging it so it is a recorded, deliberate trade rather than an unnoticed side effect of the relocation; a line under "Questions for review" would close it.

@espg espg Aug 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Leaving by design — the diagnosis is right, the fix is out of scope for this PR.

Widening the coverage means editing .pre-commit-config.yaml (files: src|tests on the mypy hook), which is lint/CI config and beyond this PR's ruled mechanical scope: the ~150 lines moved verbatim from tests/test_benchmark_shardmap.py into .github/scripts/bench_metrics.py without a semantic change. .github/scripts has never been type-checked — select_densest_shard already lived there untyped before this PR — so the move exposes a pre-existing gap rather than opening a new one.

Left standing for the repo owner to direct. If mypy should cover .github/scripts, that is its own change: widening the hook will surface findings across all five scripts, not just the moved block, and wants to land as a separate PR rather than riding a mechanical move.

# int64 microseconds cannot state and a toc word can. Output-defining: a
# store born on toc is a different product from a legacy-axis one.
time_encoding: toc
pyramid: false # overview family off: raster leaves are column-less (issue #399, issue #459)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

98ab5446 reconciled the parity test to the new state of the pair but not the other config's prose. tests/data/benchmark/configs/s2_neon_o9.yaml still opens with:

# (2025, the pinned catalog catalogs/cat_s2_neon_2025.parquet). Identical to
# the shipped src/zagg/configs/sentinel2_l2a.yaml except ``parent_order: 9`` --
# ... -- and the harness-local ``pyramid: false``
# opt-out below. Both are packaging: the semantic hash (D19) of this config
# equals the shipped one, so the leg measures the shipped product.

With this line added, the opt-out is no longer "harness-local" and no longer a divergence at all — both sides declare it, which is exactly what the reconciled test now asserts:

# cannot drift silently. Divergence budget: parent_order -- packaging, so
# the semantic hash (D19) matches. (The pyramid opt-out left the budget when

So the two files now state contradictory divergence budgets ("parent_order + pyramid" vs "parent_order"), and nothing mechanical checks the config's comment — the next reader of s2_neon_o9.yaml is told the leg deviates from the shipped product on a knob it no longer deviates on.

Secondary: the bench config's inline rationale is also the superseded one — pyramid: false # overview sweep opted out pending Phase E fleet sizing (issue #201) reads as a temporary sizing hold, where the ruled reason (pinned by the new test_sentinel2_l2a_config_declares_the_overview_family_off, and written on this line) is that raster leaves are column-less by construction (issue #399/#459).

Fix: in s2_neon_o9.yaml, drop pyramid from the "except ..." list in the header (leaving parent_order: 9 as the whole budget) and restate the inline comment as "restated from the shipped config (issue #459), not a divergence" — mirroring how store_layout: hive is already described there as "restated here, not a divergence".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Fixed in 2d7d914. Comment prose only — no YAML value touched (git diff on the file is 6 comment lines in, 4 out).

Header block: parent_order: 9 is now stated as the whole divergence budget (matching test_s2_neon_o9_tracks_the_shipped_sentinel2_config's "Divergence budget: parent_order"), and pyramid: false is called out as explicitly not a divergence because the shipped config declares the overview family off too (issue #459, issue #399). The "harness-local … Both are packaging" framing is gone.

Inline comment on the knob: # overview sweep opted out pending Phase E fleet sizing (issue #201)# overview family off, as shipped (issue #399, issue #459), tracking the shipped line's citation.

Verified: pytest tests/test_raster_benchmark.py tests/test_benchmark_shardmap.py -p no:randomly → 19 passed, 5 skipped, including test_s2_neon_o9_tracks_the_shipped_sentinel2_config, which is what pins the D19 semantic hash equality — unaffected, as expected for a comment-only edit.

Comment thread tests/data/benchmark/README.md Outdated

> **Re-pinning a map that already exists** is one command:
> `tools/repin_benchmark_shardmaps.py` (issue #444) rebuilds through the
> drift check's own recipe, selects the pin (nested rule included), prunes

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude (review)

Stale after 57364d5a: "rebuilds through the drift check's own recipe". As of that commit the recipe is no longer the drift check's — rebuild_shardmap/select_pin live in .github/scripts/bench_metrics.py, and the drift check is one of two callers, not the owner. 57364d5a rewrote exactly this phrasing everywhere else it appeared (the driver's module docstring, repin()'s docstring, the entry() note, and the test module's section comment: "Both CALL bench_metrics.rebuild_shardmap and bench_metrics.select_pin") but missed this README block, which this same PR added.

It matters here because the sentence is the one that tells a re-pinner where the shared logic lives, and the whole point of the fold was that a tools/ script must not depend on a test module for core logic.

Fix: "rebuilds through the shared recipe in .github/scripts/bench_metrics.py (the same one the drift check runs through)".

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 from Claude

Fixed in 7307a38. The re-pin block now reads that the driver "rebuilds through the shared recipe in .github/scripts/bench_metrics.pyrebuild_shardmap and select_pin, which the drift check and this driver both call", replacing "rebuilds through the drift check's own recipe". That matches the wording 57364d5 landed at the four other sites (the tool docstring, its sys.path comment, and repin()), where the recipe stopped being the guard's and became shared.

The pre-existing python fence just above (~line 176) is untouched — ruff format --check still reports it as the one README finding, deliberately left as before.

@espg

espg commented Aug 24, 2026

Copy link
Copy Markdown
Member Author

🤖 from Claude

espg's rulings on the four "Questions for review" (in-session, 2026-08-24) are applied; each question is recorded RESOLVED in the body with its rationale.

Q Ruling Action
1 — sweep-dispatcher hardening No action. The config fix already removed the false declaration (the reader-facing half); both suppression mechanisms add surface across the four gates to save one fire-and-forget Event invoke; and the no-op class is shrinking (zagg#508 admits waveform digests to the overview family, issue #399 will cover raster) body only
2 — ~65 s acceptance test Keep it, both arms. The cost is accepted: it is the only proof the re-pin driver reproduces the committed artifacts, and the nested_in arm stays body only
3 — driver imports the guard Do the mechanical move. rebuild_shardmap / select_pin (+ private plumbing) now live in .github/scripts/bench_metrics.py beside select_densest_shard; the guard, the driver, and tests/test_benchmark.py all import from there. No workflow yaml touched 57364d5
4 — pruned/note prose Carry, don't generate. Verbatim copy + printed reminder stays; no templating body only

Also this round:

  • Merged origin/main (819ddeb, regular merge — the branch was ~246 commits behind). PR small fixes 2026-08-17 (2): s2_neon_o9 toc flip, CHANGELOG entry for PR #447 #455 had landed second-half of the body's merge-order note, so test_s2_neon_o9_tracks_the_shipped_sentinel2_config failed exactly as anticipated; 98ab544 applies the pre-agreed reconciliation (pyramid out of the divergence budget, covered by the b == s comparison, parent_order the whole budget).
  • Second fresh-context adversarial review over the merge + rulings diff: 4 inline findings, no correctness bugs. Three folded (8aed187 docstring side-effect claim, 2d7d914 s2_neon_o9 comment reconciliation, 7307a38 README recipe pointer); one left standing by design — mypy does not cover .github/scripts/ (files: src|tests), and widening that is a lint-config change outside this PR's ruled scope.
  • Re-verified green post-merge: full suite 4691 passed / 38 skipped / 1 failed — the one failure (test_lambda_build.py::test_function_build_succeeds) reproduces identically on clean origin/main in this environment (local Python 3.10 cannot satisfy zarr>=3.1.5), so it is environmental and pre-existing; CI's Linux runners pass it. Both acceptance arms pass through the new bench_metrics path; driver smoke reproduces pin 11530143033882312713 at 5,536 granules with only the two excused metadata deltas.

With all phases complete, findings folded, rulings recorded, and CI green on the head sha, this PR is flipped ready for review per §2. (The earlier reverted flip on 2026-08-17 was that run's harness restriction, not a repo rule; this run is authorized to flip.) Merging remains yours.

@espg
espg marked this pull request as ready for review August 24, 2026 17:51
@espg
espg merged commit aef761c into main Aug 24, 2026
8 checks passed
@espg
espg deleted the claude/small-fixes-2026-08-17-4 branch August 24, 2026 17:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants