Skip to content

Expire brokered credentials, filter by release, and fix the granule b… - #55

Merged
SmithB merged 1 commit into
mainfrom
maap_s3_credentials
Sep 8, 2026
Merged

SmithB merged 1 commit into
mainfrom
maap_s3_credentials

Conversation

@SmithB

@SmithB SmithB commented Sep 8, 2026

Copy link
Copy Markdown
Owner

…lock size

Four fixes from a review of the MAAP/S3 path against the platform docs.

P1, credential expiry. get_s3fs() cached a session for the life of the process, but MAAP-brokered DAAC credentials expire -- measured against the live broker, the NSIDC response carries expiration exactly one hour out. A read phase that outlives it starts 403-ing partway through, which at a per-tile fan-out reads as a random data error rather than as an expiry. The cache now holds (filesystem, expires_at) and re-derives inside a 600 s margin. An unparseable or absent expiration falls back to a SHORT lifetime rather than an unlimited one: guessing "no expiry" from a value we failed to read is how a session outlives its credentials. Sessions with nothing to expire -- daac=None, which uses the self-refreshing default chain -- cache as before.

Exercised against the live broker: expiry parsed, repeat call cached, a session forced inside the margin re-derived, daac=None recorded no expiry, a malformed expiration warned and fell back to 40 minutes, and a real granule still read.

C6, release filtering. find_ATL11_granules() searched with no filter at all, so it returned every generation CMR holds for the short name. That is benign only while exactly one per-granule index subtree is staged -- a second would make every tile read both and double-count its data, silently. granule_release= now restricts results to one (cycles, release, version) triple, matched on granule names because CMR's own version filter selects the COLLECTION version (007) and cannot separate cycle ranges or granule versions within it.

release_spec() parses '007_cycle_03_31_v04' into ('0331','007','04'). IT IS DELIBERATELY PER-PRODUCT, WITH NO MODULE-LEVEL DEFAULT: ATL11 and ATL11XO are normally different generations -- rel_006_0331 pairs --ATL11_release= 007_cycle_03_31_v04 with --ATL11xo_version=007_cycle_03_30_v03, differing in both cycle range and version under one release -- so a single global "current release" would silently filter a crossover search by ATL11's cycles. Verified both parse distinctly and that a granule of either generation matches only its own spec.

C7, missing indices. A granule whose index was absent warned and was skipped, which silently produced a partial fit: a half-uploaded index tree (~8100 files) gave a tile fewer granules than it should have had, with nothing recorded. Now that the search is filtered to the generation we indexed, every granule that reaches the read is one we expect an index for, so its absence is a fault and raises. missing_index='skip' restores best-effort reads, and the option is threaded through all three entry points.

C5, block size. geoIndex.get_data() opened the granule with no block_size, so DEFAULT_REMOTE_BLOCK_SIZE never reached the read it was measured for -- scattered index_ranges out of a chunked, compressed granule, where Q27 measured 35 MiB against 6.6 MiB for the same window. The index-file read is left on the larger default deliberately: an index averages ~250 KiB and is read end to end.

Also: the hard-coded earthaccess.login(strategy='netrc') had a THIRD site, tilingSchema.resolve_files_for_box() (the crossover path), which the review missed and a grep found. The helper now lives in io_utils as try_earthaccess_login(), beside get_s3fs(), so the login policy and the credential policy stay together; query_ATL11_cloud keeps the old private name as a delegating alias for outside callers.

Claude-Session: https://claude.ai/code/session_013MdMEXw2s6eeGtaGcaDxpF

…lock size

Four fixes from a review of the MAAP/S3 path against the platform docs.

P1, credential expiry.  get_s3fs() cached a session for the life of the
process, but MAAP-brokered DAAC credentials expire -- measured against the live
broker, the NSIDC response carries expiration exactly one hour out.  A read
phase that outlives it starts 403-ing partway through, which at a per-tile
fan-out reads as a random data error rather than as an expiry.  The cache now
holds (filesystem, expires_at) and re-derives inside a 600 s margin.  An
unparseable or absent expiration falls back to a SHORT lifetime rather than an
unlimited one: guessing "no expiry" from a value we failed to read is how a
session outlives its credentials.  Sessions with nothing to expire -- daac=None,
which uses the self-refreshing default chain -- cache as before.

Exercised against the live broker: expiry parsed, repeat call cached, a session
forced inside the margin re-derived, daac=None recorded no expiry, a malformed
expiration warned and fell back to 40 minutes, and a real granule still read.

C6, release filtering.  find_ATL11_granules() searched with no filter at all, so
it returned every generation CMR holds for the short name.  That is benign only
while exactly one per-granule index subtree is staged -- a second would make
every tile read both and double-count its data, silently.  granule_release= now
restricts results to one (cycles, release, version) triple, matched on granule
names because CMR's own version filter selects the COLLECTION version (007) and
cannot separate cycle ranges or granule versions within it.

release_spec() parses '007_cycle_03_31_v04' into ('0331','007','04').  IT IS
DELIBERATELY PER-PRODUCT, WITH NO MODULE-LEVEL DEFAULT: ATL11 and ATL11XO are
normally different generations -- rel_006_0331 pairs --ATL11_release=
007_cycle_03_31_v04 with --ATL11xo_version=007_cycle_03_30_v03, differing in
both cycle range and version under one release -- so a single global "current
release" would silently filter a crossover search by ATL11's cycles.  Verified
both parse distinctly and that a granule of either generation matches only its
own spec.

C7, missing indices.  A granule whose index was absent warned and was skipped,
which silently produced a partial fit: a half-uploaded index tree (~8100 files)
gave a tile fewer granules than it should have had, with nothing recorded.  Now
that the search is filtered to the generation we indexed, every granule that
reaches the read is one we expect an index for, so its absence is a fault and
raises.  missing_index='skip' restores best-effort reads, and the option is
threaded through all three entry points.

C5, block size.  geoIndex.get_data() opened the granule with no block_size, so
DEFAULT_REMOTE_BLOCK_SIZE never reached the read it was measured for --
scattered index_ranges out of a chunked, compressed granule, where Q27 measured
35 MiB against 6.6 MiB for the same window.  The index-file read is left on the
larger default deliberately: an index averages ~250 KiB and is read end to end.

Also: the hard-coded earthaccess.login(strategy='netrc') had a THIRD site,
tilingSchema.resolve_files_for_box() (the crossover path), which the review
missed and a grep found.  The helper now lives in io_utils as
try_earthaccess_login(), beside get_s3fs(), so the login policy and the
credential policy stay together; query_ATL11_cloud keeps the old private name
as a delegating alias for outside callers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013MdMEXw2s6eeGtaGcaDxpF
@SmithB
SmithB merged commit 14454e8 into main Sep 8, 2026
0 of 2 checks passed
@SmithB
SmithB deleted the maap_s3_credentials branch September 8, 2026 21:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant