moczarr.coverage
Coverage-envelope decoding (morton-moc/1), read side — pure functions.
The tiered coverage convention (zagg sparse_coverage.md §4): a leaf's
commit stamp carries a coverage envelope — the tier-0 morton box (<= 4
decimal-string members, null-padded) plus an encoding discriminator:
"full"— coverage is the whole shard subtree; no sidecar exists."bitmap"— exact cell-order occupancy lives in the in-leafcoverage.mocsidecar: a zstd-compressed bit field, biti= the i-th subtree cell in ascending packed-word order (base-4 digit tail, digits1..4->0..3), MSB-first per byte.- absent — box-only (phase-1 stamps, depth-0 configs).
The store root's coverage.moc is a "ranges" envelope: inclusive
[first, last] runs of same-order shard ids within one base cell,
consecutive in digit-tail rank, endpoints as decimal STRINGS (packed words
exceed 2^53 — raw JSON numbers would be float-mangled).
Postures, inherited from the design's D9 discipline: envelopes above the
leaf are caches — an unusable one reads as absent (None) and the caller
degrades to the walk, never to a wrong answer. A PRESENT-but-corrupt bitmap
sidecar raises instead: silently zero-padding would fabricate false
negatives, indistinguishable from healthy sparse coverage.
aoi_mask(cells, aoi)
Boolean mask over cells: which intersect the AOI morton cover.
aoi is packed uint64 words, decimal strings, or an object
exposing __morton_moc__() (mortie's Moc).
The §5 nesting predicate (prefix = ancestor), applied in BOTH
directions: a cell is kept when it sits inside an AOI member (member
coarser-or-equal) or contains one (member finer). This is containment,
NOT np.isin against moc_and's output — MOC intersection returns
a compacted cover (a fully-occupied subtree compacts to its parent
word), so identity tests against it silently drop exactly the dense
regions.
BOTH sides may be mixed-order (issue #8, on mortie#116's per-element
kernels): a pyramid/overview store (zagg#262) carries a parent and its
children in one cell coordinate, and each cell resolves containment at
its own order via orders_of. The former single-order guard is gone —
the capability replaces the constraint. Per member, the coarser-or-equal
direction is ONE whole-array compare: clip2order is per-element
(finer cells clip to the member's order, coarser cells pass through
unchanged) and words at different orders can never be bit-equal (the
order lives in the word's suffix), so pass-throughs never false-match.
That same argument makes the compare provably false when the member is
finer than EVERY cell — nothing clips, so every word is a pass-through —
so it is skipped outright there: the fine-AOI-cover-against-a-coarse-
overview-level shape this PR unlocks, measured ~3.7x on 500k order-8
cells with 512 order-20 members.
The finer direction runs once per distinct coarser cell order present
(<= 30), comparing against the member's ancestor at that order.
POINT-kind words (spec §1 suffix 48..=63) are first-class members on
BOTH sides — the v1 posture: for containment a point counts as an
order-29 member like any other, normalized to its order-29 area twin
(same path; §4 — membership at a coarser level is ordinary truncation),
and may now ride alongside area cells at ANY order (previously only
order-29 areas kept a mixed-kind array uniform).
Source code in src/moczarr/coverage.py
264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | |
box_and(coverage, aoi)
Intersection of a leaf envelope's tier-0 box with an AOI morton cover.
One in-memory op on <= 4 members — the cheap AOI reject a reader runs on
the stamp it already fetched, before paying for the bitmap sidecar.
aoi is packed uint64 words, decimal strings, or an object
exposing __morton_moc__() (mortie's Moc), mixed orders allowed. An
empty result rejects the leaf outright (the box is a conservative
superset: false positives possible, false negatives impossible).
Source code in src/moczarr/coverage.py
249 250 251 252 253 254 255 256 257 258 259 260 261 | |
box_words(coverage)
The tier-0 box members as packed uint64 words (nulls dropped).
Feed to mortie.moc_and against an AOI cover for the cheap leaf
reject: the box is a conservative superset (false positives possible,
false negatives impossible).
Source code in src/moczarr/coverage.py
126 127 128 129 130 131 132 133 134 | |
coverage_moc(envelope, *, store=None, **store_kwargs)
The root envelope's spatial coverage as a :class:mortie.Moc — or raise.
Raises whatever :func:ranges_words raises, deliberately and
undegraded: a missing ranges (or order) key is KeyError, a
malformed range — base-crossing, reversed, off-order — is
ValueError. There is no absence arm here at all; a caller who
wants one asks :func:parse_root_coverage first. Corrupt CONTENT must
never yield a plausible partial cover, which is the whole reason
ranges_words validates every range before parsing any of them.
Root form (issue #49): a str first argument is the STORE ROOT, and
the envelope is fetched before the cast — coverage_moc(root,
anonymous=True) — one metadata GET through
:func:moczarr.store.load_root_coverage, store= and
**store_kwargs exactly as that function takes them. The no-absence
posture holds: a root whose envelope is missing or unusable raises
ValueError (there is no Moc to hand back, and the root envelope
is a regenerable CACHE — its absence says nothing about the store's
actual coverage, so no empty or degraded cover is fabricated in its
place). A store-level failure that signals as anything other than a
404 — bad credentials, wrong region, 5xx, DNS — propagates untouched,
and a 404-shaped root (missing bucket, mistyped prefix), which the
transport cannot tell from an absent sidecar, is settled by probing
morton_hive.json on the absence path only (one extra GET): a root
carrying no manifest raises the library's house ValueError (not a
hive store root) instead, so absence of coverage is never reported
for absence of a store. The dict form is unchanged; store
arguments with a dict are a TypeError.
The cast to the geometry world (issue #45): moczarr parses its own
storage grammar — the "ranges" encoding of :func:ranges_words —
and mortie types the result. The direction is deliberate and was ruled
in design review: a Moc.from_envelope on mortie's side was
rejected, because the envelope grammar is moczarr's to read, not
mortie's to know. :func:moczarr.store.load_root_coverage is unchanged
and still returns the envelope dict; this is a cast a caller asks for,
never one the read path takes on its way anywhere.
NOT the containment hot path. Two costs ride along, and both are
exactly what the pruning path exists to avoid: the ranges are
EXPANDED to O(covered shards) words (a CONUS/Antarctic root MOC is
thousands), and Moc then normalizes them eagerly through
compress_moc, so the words it holds are the COMPACTED equivalent
cover — a fully-occupied set of four siblings becomes its parent, and
coverage_moc(envelope).words is therefore not
ranges_words(envelope) element-for-element (same coverage, fewer
words, mixed orders). For "does this envelope list this shard", reach
for :func:ranges_contain instead: rank space, O(ranges), no
expansion and no compaction. Use this when a consumer wants coverage
it can intersect, refine, or hand to a geometry API — including back
into this package, since Moc satisfies the __morton_moc__()
protocol every AOI seam here accepts.
Source code in src/moczarr/coverage.py
575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 | |
coverage_toc(envelope, *, store=None, **store_kwargs)
The envelope's §10 tier-1 temporal coverage as a :class:mortie.Toc,
None when the store publishes none — or raise.
Root form (issue #49): a str first argument is the STORE ROOT, and
the envelope is fetched before the cast — coverage_toc(root,
anonymous=True) — one metadata GET through
:func:moczarr.store.load_root_coverage, store= and
**store_kwargs exactly as that function takes them. TWO distinct
absences then collapse into the one None: a root with no usable
coverage sidecar at all (that function's None), and a sidecar with
no usable temporal section (this function's own None arm, below).
Both already mean the same thing to a caller — this store publishes no
usable temporal coverage — so they share the answer deliberately; a
caller who must tell them apart fetches the envelope itself and passes
the dict. An UNREACHABLE store still RAISES rather than answering
None: a store-level failure signalling as anything other than a
404 propagates untouched, and a 404-shaped root — missing bucket,
mistyped prefix, indistinguishable from an absent sidecar at the
transport — is settled by probing morton_hive.json on the absence
path only (one extra GET), a root carrying none raising the library's
house ValueError (not a hive store root). Absence of coverage is
not absence of a store. The dict form is unchanged; store
arguments with a dict are a TypeError.
The two arms are not the same failure and the split is deliberate: an
UNREADABLE section degrades to None (below), while CORRUPT CONTENT
inside a well-formed one raises, exactly as
:func:temporal_shard_words documents — a shard id off the carrier's
order or a word that is not a uint64 decimal string is ValueError,
a carrier missing order is KeyError. So a caller who dutifully
handles None is still not handling everything: degradation stops
at the section gate, and past it a corrupt cache must never yield a
plausible partial cover.
The temporal twin of :func:coverage_moc, same direction: moczarr
decodes its own section grammar (:func:temporal_shard_words) and
mortie types the words. Toc normalizes eagerly (through
toc_normalize), so the result is the canonical gappy cover over
every listed shard's envelope word — the union, which is what "when
does this store hold data" asks.
The temporal mirror of the compaction warning :func:coverage_moc
carries: normalization is lossy toward coverage, so a word whose span
is SUBSUMED by another word's is ABSORBED at construction, and
coverage_toc(envelope).words is therefore not
temporal_shard_words(envelope)[1] element-for-element (same
coverage, fewer words). It is neither row-aligned with the tier-1 map
nor the same length, so a caller cannot index shard i's word out of
it. For per-shard words, and for anything needing the shard→word row
alignment, use :func:temporal_shard_words.
None is the whole point of the signature. It means the store
publishes no READABLE temporal coverage — the section is missing, or
is at a revision this reader does not know, or lacks the shards
key §10.1 requires (one shared gate, _usable_temporal, so this
agrees with :func:parse_root_coverage even when a caller hands in an
envelope that never went through it), or lists no shards at all. §10
says that state means
"this store publishes no temporal coverage", which is
NOT the same claim as "this store has no data in any window" — and an
empty Toc would say the second: .overlaps(q) would answer
False for every query, a false negative of exactly the kind §10's
absence rule exists to forbid. So absence surfaces as None (the
house idiom — :func:parse_root_coverage and
:func:moczarr.store.load_root_coverage already read "no usable
envelope" that way) and the caller must decide, loudly, rather than be
handed a confident wrong answer. An empty Toc is therefore NOT
reachable through this function, and a caller never has to tell one
apart from a real cover: a section that lists NO shards is §10.2's
unlisted-is-unknown rule applied to every shard at once — nothing
was rolled up, so nothing is known, which is the same epistemic state
as no section at all and takes the same None arm.
The same staleness posture rides along one level up: the tier-1 map
lists only shards a producer rolled up, so this cover is a claim about
LISTED shards, not about the store. For per-shard pruning use
:func:temporal_keep (or candidate_leaves(..., when=)), which
keeps unlisted shards by construction. Tier-2 density queries ("how
MUCH data in this window") are not on this surface at all: the
digest block is a t-digest in the store's native forms and rides
the existing CDF machinery (:mod:moczarr.stats), per the design
ruling that it gets no new moczarr surface.
Source code in src/moczarr/coverage.py
642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 | |
decode_bitmap(payload, shard, cell_order)
Occupied cell words from a bitmap-sidecar payload — exact, or raise.
Returns the sorted packed uint64 words at cell_order whose bits
are set. A corrupt payload — zstd garbage, or a decompressed size other
than the deterministic ceil(4^depth / 8) bytes — raises rather than
zero-padding to a plausible partial cell set (a false negative; the
exact truth is intact in the leaf, so surfacing beats under-reporting).
Source code in src/moczarr/coverage.py
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
parse_leaf_coverage(stamp)
The coverage envelope from a commit stamp, or None when absent.
Tolerant by design: debris (None stamp), pre-coverage stamps, a
malformed payload, or an unknown/future spec all read as absent — the
box tiers are indexes, never truth, so a reader without them degrades to
opening the leaf. Strict on the spec gate: a future envelope version
must be adopted deliberately, not half-parsed.
Source code in src/moczarr/coverage.py
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | |
parse_root_coverage(payload)
A usable store-root coverage envelope, or None.
The root MOC is a regenerable cache: a non-mapping payload, an unknown
spec, or a non-"ranges" encoding reads as absent and the caller
falls back to the discovery walk (D9 — degrade, never wrong answers).
The temporal section (zagg spec §10, issue #45) rides the same
carrier under its own versioned-key discipline, strict-gated on its own
spec marker: a section declaring exactly :data:TEMPORAL_SPEC AND
carrying the §10.1-required shards mapping is carried through; an
unknown revision, a malformed non-mapping value, or a section whose
shards is missing or is not a mapping reads as ABSENT (dropped from
the returned envelope), never as an error, because the section is an
accelerator whose truth is in the leaves — a section missing its one
required tier-1 key is structurally malformed, so the same posture
extends to it. Whole-section absence means only "this store publishes no
temporal coverage": none of these cases refuses the store, the sidecar,
or a windowed query (§10's absence rule).
Source code in src/moczarr/coverage.py
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | |
ranges_contain(envelope, shard)
Whether the envelope's ranges list one shard id — O(ranges), no expansion.
Source code in src/moczarr/coverage.py
327 328 329 330 331 332 333 334 335 336 337 338 339 | |
ranges_words(envelope)
Shard words from a root envelope's ranges — exact expansion, or raise.
Malformed ranges (base-crossing, wrong order, reversed endpoints) raise:
a corrupt cache must never yield a plausible partial answer — every
range is validated BEFORE any of them is parsed. Expansion is O(covered
shards) through ONE batched mortie.decimal_to_word call rather than
a per-shard crossing (this runs on every root-MOC-backed open, and a
CONUS/Antarctic root MOC is thousands of shards); containment checks on
the hot path should use :func:ranges_contain instead (rank space, no
materialization).
Source code in src/moczarr/coverage.py
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | |
root_coverage_and(envelope, aoi)
Intersection of the root ranges MOC with an AOI morton cover.
aoi is any morton cover — packed uint64 words, decimal strings,
or an object exposing __morton_moc__() (mortie's Moc), mixed
order allowed (mortie's moc_and
resolves containment across orders). Returns the covered shards the AOI
touches; empty means no covered shard intersects. Expansion is
O(covered shards) — see :func:ranges_words.
Source code in src/moczarr/coverage.py
234 235 236 237 238 239 240 241 242 243 244 245 246 | |
temporal_keep(shard_words, envelope, when_words)
Mask over candidate shards: which MAY hold data in the window (§10).
The pruning asymmetry, exactly as ruled (issue #45): a shard the tier-1
map LISTS is dropped only when its word fails mortie.toc_overlaps
for every query word's window; a shard ABSENT from the map is always
KEPT — the map lists only what a producer rolled up, so unlisted is
unknown, never empty (§10.2: not proof the shard has no data in
the window). An envelope with no usable temporal section keeps
everything. Each query word's conservative envelope (toc2time)
becomes the predicate's half-open window; a timestamp word queries its
exact instant as [t, t+1) — the grammar's empty window matches
nothing, which would silently invert a timestamp's meaning.
That decode WIDENS the query (a range word's envelope is already
outward-rounded), on top of the quantum toc_overlaps adds for the
shard's own word — so the mask over-reports by up to two quanta near
window edges and never under-reports. It is also the only route
available: mortie exposes no word-vs-word overlap predicate, and §10.2's
MUST is about not decoding the SHARD words, which this never does.
Source code in src/moczarr/coverage.py
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 | |
temporal_shard_words(envelope)
The §10 tier-1 map as row-aligned (shard_words, toc_words) — or raise.
Decodes temporal.shards — D1 decimal shard ids at the carrier's
order mapped to one toc word each, spelled as decimal strings
because a uint64 exceeds 2^53 and a float-based JSON parser would
silently mangle a raw number (§10.2) — into two uint64 arrays,
ascending in shard packed-word order. Both come back EMPTY when the
envelope carries no temporal section, one at a revision other than
:data:TEMPORAL_SPEC, or one whose shards is missing or is not a
mapping. §10's strict-check-then-absent rule, enforced HERE as well as
at :func:parse_root_coverage's gate: the decoder is reachable with an
envelope that never passed that gate (json.loads straight in), and
an unknown revision must never decode under v1 semantics — keys are
never repurposed in place, so a /2 map may mean something else
entirely. §10.1 makes shards required in turn, so a section without
a usable map is STRUCTURALLY malformed and takes the same posture.
§10's absence rule throughout — no listing is not a claim of no data,
so a caller prunes nothing.
CONTENT-level corruption inside a well-formed map raises instead (the
:func:ranges_words posture: a corrupt cache must never yield a
plausible partial answer): a key not at the carrier's order, or a word
value that is not a uint64 decimal string. Every entry is validated
BEFORE any word is returned.
The words are the grammar's join over every §8.3 companion the shard's
leaves hold — feed them to mortie.toc_overlaps/toc_contains
(§10.2: a reader uses the grammar's predicates on the words, never its
own decoded-bound compare). A shard absent from the map is unknown,
never empty: its temporal contribution has not been rolled up yet.
Source code in src/moczarr/coverage.py
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 | |