Adopt zarr-metadata + add strict Zarr v3 validation (Core/Extra)#173
Draft
d-v-b wants to merge 89 commits into
Draft
Adopt zarr-metadata + add strict Zarr v3 validation (Core/Extra)#173d-v-b wants to merge 89 commits into
d-v-b wants to merge 89 commits into
Conversation
Design for adopting zarr-metadata>=0.3.0 in stable v2/v3: typed to_json/to_store_json serialization, replacing hand-rolled internal types, and an opt-in strict ArraySpec (per-dtype discriminated union coupling data_type with fill_value). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Significant release: drop back-compat shims (remove NamedConfig export in favor of NamedConfigV3 and other replaced exports), add migration guide to the docs plan. ArraySpec remains loose-by-default. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
raw r<N> is a NewType(str), not a Literal, so it can't be a Field(discriminator) member. Strict spec is a hybrid: discriminated union over literal-named dtypes, smart-unioned with the raw member. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
10-task TDD plan: dependency, v3/v2 type replacement, typed to_json/to_store_json, _BaseArraySpec extraction, StrictArraySpec (hybrid discriminated+raw union), drift guard, docs/migration, full gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Dict) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…BREAKING) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-requested scope addition. StrictGroupSpec.members must recursively be StrictArraySpec/StrictGroupSpec; group's own fields unchanged. Inserted as Task 8; drift-guard/docs/gate renumbered to 9/10/11. Mechanism verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…BREAKING) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…iblings Move all shared fields (node_type, attributes, shape, storage_transformers, dimension_names) and all methods (validate_dimension_names, model_dump, to_json, from_array, from_zarr, to_zarr, like) to a new _BaseArraySpec base class. ArraySpec becomes a thin concrete subclass that only declares the five variant fields (data_type, chunk_grid, chunk_key_encoding, fill_value, codecs). The cls(...) calls in from_array/from_zarr pass variant fields as keyword arguments with # type: ignore[call-arg] because mypy cannot see the subclass fields when cls is typed as type[Self]=type[_BaseArraySpec]; the ignore is minimal and load-bearing. ruff PIE804 required the dict-unpacking workaround to be abandoned in favour of direct kwargs + suppression. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… ignore Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Avoid leaking the private _BaseArraySpec into the public like() signature; matches the GroupSpec.like/AnyGroupSpec convention. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…value Implements _strict_v3.py with 15 private per-dtype array spec classes (_BoolArraySpec through _RawArraySpec), each pairing the zarr-metadata <X>DataTypeName Literal with <X>FillValue, plus strict chunk_grid, chunk_key_encoding, and codec validation. StrictArraySpec is a hybrid union: a discriminated union of the 14 Literal-dtype specs plus _RawArraySpec (NewType, cannot be a discriminator member). Re-exported from v3.py at the bottom to avoid circular import. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
StrictGroupSpec is a concrete NodeSpec subclass whose members field accepts only StrictArraySpec or StrictGroupSpec values, propagating dtype+fill_value strictness through the entire hierarchy. Also adds a default for attributes in _StrictBase so array specs can be used without explicitly supplying an empty attributes mapping. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…wing override) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…zed)
Decided 2026-06-19: strict per-dtype array members and StrictGroupSpec
drop TAttr/TItem; attributes: Mapping[str, object] = {}; strict group
members fixed to StrictArraySpec | StrictGroupSpec. Loose specs keep generics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Strict per-dtype array members and StrictGroupSpec drop TAttr/TItem;
attributes: Mapping[str, object] = {}. Loose specs keep generics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d union) Examined 'every strict array is also a loose array': the per-dtype discriminated union already expresses the data_type<->fill_value correlation with static precision, and strict fill-value types are verified subtypes of JSONValue (value-level substitutability). Rejected inheritance (narrowing fails mypy), runtime-only enforcement (loses static precision), and Protocol+drop-base (cost > benefit). Keep current sibling design. No code change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Re-examined per maintainer request: generic strict forces StrictArraySpec[TAttr] as a generic union alias (mypy rejects unparametrized union of generic members). Kept non-generic; typed attributes available via loose ArraySpec[MyAttrs]. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tArraySpec union StrictArraySpec becomes a single constructible class (weak fill_value annotation + model_validator runtime coupling). Public per-dtype classes (Float64ArraySpec etc.) give static precision. AnyStrictArraySpec is the discriminated union / validation target. StrictGroupSpec.members -> AnyStrictArraySpec | StrictGroupSpec. Mechanism verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ic per-dtype + AnyStrictArraySpec Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two strict vocabulary levels: Core (core spec only) and Extra (core + zarr extensions), differing in chunk_grid (regular vs +rectilinear) and codecs (7 vs 9 + known-name-literal strings only, no arbitrary str). Naming: ArraySpec stays loose default; Core*/Extra* for strict (no Strict prefix). Approach A: plain non-generic per-dtype classes (~30). Replaces interim Strict* names. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dim_of) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add chunk_grid implementations for regular and rectilinear chunk grid types, including builder functions, validation, and ndim extraction helpers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace bare `dict` types with `RegularChunkGridMetadata` and `RectilinearChunkGridMetadata` in chunk grid modules' builder functions and validators. Imports use TYPE_CHECKING pattern matching codec modules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Implement codec/__init__.py and chunk_grid/__init__.py: - Re-export builders from all 9 codec modules and 2 grid modules - Build CODEC_NDIM_OF, CODEC_KIND, CODEC_DTYPE_OUT, CODEC_VALIDATE dispatch maps keyed by codec name - Build GRID_NDIM_OF, GRID_VALIDATE dispatch maps keyed by grid name - Move _CoreCodec/_ExtraCodec unions here (identical membership to _strict_v3.py) - Re-export RegularChunkGridMetadata/_RectilinearChunkGrid type aliases - Add test_dispatch_maps.py verifying map coverage and spot-checks Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dator Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…type-correctness) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…re internal validators - Delete inline _CoreCodec/_ExtraCodec union definitions from _strict_v3.py; import them from pydantic_zarr.strict.v3.codec (identical membership). - Remove now-unused zarr_metadata codec type imports (all *CodecMetadata and *CodecName names previously used only to build the inline unions). - Add _validate_codec_internal / _validate_grid_internal dispatch functions that look up CODEC_VALIDATE / GRID_VALIDATE and run the per-element validate_<x> function when the parsed value is a dict (object form). - Wire AfterValidator(_validate_codec_internal) onto the codecs tuple element type and AfterValidator(_validate_grid_internal) onto chunk_grid in both _CoreBase and _ExtraBase, so parsed-from-JSON specs get internal checks. - Add three targeted rejection tests: non-permutation transpose order, blosc clevel 99, non-positive regular chunk_shape. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…array validator Add `_validate_array_consistency` model_validator (mode="after") to both `_CoreBase` and `_ExtraBase` that calls `check_array_consistency` and `validate_pipeline`, concatenates violations, and raises a single `ValueError` listing all errors. Also fix `_resolve_strict_init` so that the default chunk_grid shape matches the array shape exactly (was using `shape_v or (1,)` which created an inconsistent 1D chunk for 0-dim arrays). Updated 6 existing tests that were relying on the validation gap (incomplete pipelines now correctly rejected): - test_core_accepts_known_codec_name_string: add bytes codec before blosc - test_extra_accepts_known_codec_name_string: same - test_extra_accepts_scale_offset_codec: add bytes codec after scale_offset - test_extra_accepts_scale_offset_codec_name_string: same - test_core_array_spec_bare_construct: fixed by _resolve_strict_init fix - test_core_codec_matches_oracle / test_extra_codec_matches_oracle: add _codec_pipeline_for() helper to avoid adding a second array->bytes codec when the candidate itself is array->bytes (e.g. "bytes") Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…pipeline checks Added to strict_oracle.py (stdlib-only, no pydantic-zarr imports): - is_valid_codec_internal(name, meta): encodes transpose permutation rule, blosc clevel in [0,9], gzip level in [0,9], sharding/regular chunk_shape all-positive, other codecs -> True. - is_valid_ndim_match(shape, chunk_grid): checks array ndim == regular chunk_grid ndim. Added to test_strict_property.py (hypothesis @given tests): - test_transpose_internal_matches_oracle: generates transpose orders (valid permutations + arbitrary int lists); asserts accept iff oracle says permutation. - test_blosc_clevel_matches_oracle: generates clevel in [-3, 12]; asserts accept iff clevel in [0, 9]. - test_regular_grid_ndim_matches_oracle: generates (array_ndim, chunk_ndim) pairs; asserts accept iff equal. Weaken/restore gap proof (Step 3): Temporarily commented out the permutation check in validate_transpose. Hypothesis immediately found: order=[1] accepted=True but oracle says False (order [1] is not a permutation of range(1)=[0]). Test FAILED with: AssertionError: order=[1]: accepted=True but oracle says False After restoring the check all 33 tests PASS. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Catch only ValidationError (not any Exception) so an unexpected error surfaces as a test failure instead of being treated as a rejection. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Also fix a pre-existing failing doc example: the rectilinear chunk grid example used chunk_shapes=(5,5,5,5) (4-D) against a 1-D array in COMMON, which the new dimensionality validation correctly rejected. Updated to chunk_shapes=(5,) so ndim matches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…TypeError) A bare-string transpose/sharding_indexed codec (valid vocabulary names) crashed the dimensionality + divisibility passes with an uncaught TypeError (ndim_of/config indexing assumed a dict). Guard on isinstance(c, dict); bare-string codecs contribute no dimensionality constraint. Add the two names to the property strategy + a direct regression test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…KeyEncodingSpec + registry) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace deleted CODEC_NDIM_OF/GRID_NDIM_OF parallel maps with grid_spec_for/codec_spec_for registry lookups. Guard ndim_of calls with isinstance(dict) so bare-string codecs never reach config indexing — making the TypeError crash structurally impossible. Replace local _name helper with element_name from _registry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dent_config Replace CODEC_KIND/CODEC_DTYPE_OUT parallel maps and _name() helper with codec_spec_for() registry lookups; has_dtype_dependent_config replaces the hardcoded "cast_value" name check. Dtype-threading semantics preserved. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ire CKE validator - Replace GRID_VALIDATE/CODEC_VALIDATE dispatch-map imports with grid_spec_for/codec_spec_for/key_encoding_spec_for from the new per-element spec packages. - Import _ChunkKeyEncoding from strict.v3.chunk_key_encoding (removes the local duplicate definition and drops the now-unused DefaultChunkKeyEncodingMetadata/V2ChunkKeyEncodingMetadata imports from zarr_metadata). - Rewrite _validate_codec_internal and _validate_grid_internal to look up specs via the registry functions; add _validate_key_encoding_internal using key_encoding_spec_for. - Wire AfterValidator(_validate_key_encoding_internal) onto chunk_key_encoding on both _CoreBase and _ExtraBase. - Update test_dispatch_maps.py to use the new CODECS/GRIDS registry dicts (old CODEC_VALIDATE/GRID_VALIDATE symbols were removed in a prior task, breaking collection). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rewrote _CoreCodec/_ExtraCodec unions so bare *CodecName members appear only for config-optional codecs (bytes, crc32c, scale_offset). Config-required codecs (gzip, zstd, blosc, transpose, sharding_indexed, cast_value) now contribute only their *CodecObject form. Added test_bare_string_accepted_iff_config_optional and test_object_form_always_accepted_for_member_codecs to test_registry.py to lock this invariant. Updated existing tests that asserted the buggy bare-acceptance: - test_strict_v3.py: test_core/extra_accepts_known_codec_name_string changed from bare "blosc" (config-required, now rejected) to bare "crc32c" (config-optional) - test_strict_property.py: _CODECS strategy changed from bare strings for config-required codecs to minimal valid object dicts so oracle property tests remain a vocabulary check without depending on bare-string acceptance Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ross elements
Add _CONFIG_REQUIRED_NAMES frozenset and bare_string_allowed() to the independent
oracle (stdlib only, no pydantic_zarr/zarr_metadata import). Hand-encoded set covers:
gzip, zstd, blosc, transpose, sharding_indexed, cast_value, regular, rectilinear.
Config-optional (bare allowed, not in set): bytes, crc32c, scale_offset, default, v2.
Add three property tests to test_strict_property.py:
- test_bare_string_codec_accept_iff_oracle: feeds all 11 codec name strings as bare
strings and asserts AnyExtraArraySpec accepts iff is_valid_codec("extra", name) and
bare_string_allowed(name). All 36 tests pass.
- test_default_cke_object_and_bare_accepted: both object and bare "default" accepted.
- test_v2_cke_object_and_bare_accepted: both object and bare "v2" accepted.
Prove-catches-a-gap: temporarily added TransposeCodecName back into _ExtraCodec union
(re-introducing the bare-transpose acceptance bug). Result:
AssertionError: bare codec 'transpose': accepted=True but oracle says False
Falsifying example: test_bare_string_codec_accept_iff_oracle(codec='transpose')
Union restored; git status src/ clean before this commit.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
find_examples('docs') recursively picked up the gitignored
docs/superpowers working area (design specs / plans), whose code
blocks are illustrative pseudocode, not runnable examples. Scope the
doc-example test to published docs only.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ntic_zarr.v3
Writing the validation-error examples surfaced that the strict builders
were only reachable via deep private-looking paths
(pydantic_zarr.strict.v3.codec.transpose) — every codec example needed a
separate deep import or a wall of nested {name, configuration} dicts. Made
all 13 builders (codecs, grids, chunk-key-encodings) importable directly
from pydantic_zarr.v3, and updated the existing builder docs to use them.
Added a 'Validation errors strict mode catches' section to usage_zarr_v3.md
demonstrating: build-time ValueError from builders; spec-level dimensionality,
sharding-divisibility, pipeline-shape, and cast_value scalar-dtype errors;
and parsed-dict rejection of bare-string codecs that require configuration.
Examples use the single-class route + exc.errors()[0]['msg'] for clean,
quotable messages.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The public-API study found that adopting zarr-metadata changed the loose ArraySpec/GroupSpec field TYPES (names unchanged) and some validation behavior, which the removed-exports changelog/migration did not cover: - v2 ArraySpec.filters and v3 chunk_grid chunk_shape are now tuples on the model / model_dump() (to_json() output unchanged — still JSON arrays); - v3 chunk_grid/chunk_key_encoding/codecs now accept the bare-string short form (previously rejected); - fill_value/dtype are now JSONValue / zarr-metadata dtype types. Add a changelog removal fragment + a 'Loose-spec behavioral changes' table to the migration guide so no breaking change ships undocumented. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…x changelog tuple wording
Under numpy 2.5, DateTime64/TimeDelta64.default_scalar() returns a generic-unit
np.datetime64('NaT')/np.timedelta64('NaT'), which numpy 2.5 deprecates. The dtype-example
fixtures in conftest exercise these types, so with filterwarnings=['error'] the warning failed
conftest collection across the whole CI matrix. Allow-list this specific zarr-python-originating
DeprecationWarning (narrow match: exact message + category), matching the existing pattern for
known zarr-python interactions. Reproduced under numpy 2.5.0 + zarr 3.1.0: test_pydantic_zarr
suite passes (9844 passed).
Also corrects the loose-spec changelog/migration wording: the list->tuple change is about
making the parsed spec immutable, not about JSON correctness (both serialize to JSON arrays).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename all changelog fragments from the +name.type.md style to the repo's required <issue>.<type>.md form (PR zarr-developers#173). The CI 'Check changelog entries' job (ci/check_changelog_entries.py, pre-existing on main) requires an integer issue number before the type; the + fragments all failed it, and '+numpy-2.5-...misc.md' additionally broke the type split on the '2.5' dot. - Recategorize the numpy-2.5 timedelta entry misc -> bugfix (it's a real test fix, and misc fragments are content-less by convention). - Correct the loose-spec migration wording: the loose specs validate named-config fields as SYNTAX ONLY (they don't know that e.g. a 'regular' chunk grid requires configuration -- that's the strict specs' job), so 'chunk_grid="regular"' was a misleading example implying it's meaningful. Reframe around the actual change (generic str|NamedConfigV3 typing) and drop the confusing example. Also drop a stale deep-path builder reference now that builders are public from pydantic_zarr.v3. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Integrates the
zarr-metadatapackage (>=0.3.0) as the source of truth for Zarr metadata types, and adds strict Zarr v3 validation in two families (Core/Extra). Hand-rolled type definitions that duplicatedzarr-metadataare removed.This is a breaking release. Every breaking change is captured in a changelog fragment under
changes/and in the migration guide.What's new (additive)
pydantic_zarr.v3:CoreArraySpec, per-dtype classes (CoreInt32ArraySpec,CoreFloat64ArraySpec, … 15 dtypes),AnyCoreArraySpecdiscriminated union,CoreGroupSpec. Restricts chunk grids toregularand codecs to the seven core-spec types.ExtraArraySpec+ mirrors; additionally acceptsrectilinearchunk grids andscale_offset/cast_valuecodecs.fill_value↔data_typecoupling (e.g.float64accepts"NaN",int64does not) and reject unknown dtypes / codec names.pydantic_zarr.v3:bytes_codec,blosc_codec,transpose_codec,sharding_indexed_codec, … plusregular_grid/rectilinear_gridanddefault_chunk_key_encoding/v2_chunk_key_encoding. Each returns a validated metadata dict.to_json()on v2/v3ArraySpec/GroupSpec;to_store_json()on v2..create()(sensible defaults for every field), strict.from_array(), andcreate_with_sharding(outer_chunk_shape=…, inner_chunk_shape=…).Breaking changes
Removed exports (replaced by
zarr-metadata)NamedConfig/AnyNamedConfig,RegularChunking(Config),Default/V2ChunkKeyEncoding(Config), theFillValueunion +*FillValuealiases,MemoryOrder,DimensionSeparator,CodecDict. Full replacement table in the migration guide.Loose-spec behavioral changes
Field names on the loose
ArraySpec/GroupSpecare unchanged, but adoptingzarr-metadatachanged some field types and validation:listtuple(v2filters, v3chunk_shape) —to_json()output is unchanged (still JSON arrays)chunk_grid="regular", etc.)fill_value/dtypetypezarr_metadata.JSONValue/ zarr-metadata dtype types (loose specs still validate as syntax only)Stricter
fill_valuevalidationThe
Core/Extrastrict specs reject invalidfill_values that were previously accepted (non-hex float strings, out-of-range / non-whole integers, booleans for integer dtypes, malformed complex components, out-of-range raw bytes).Migration
docs/migration.mdcovers every removed symbol + replacement, the loose-spec behavioral changes, the new serialization methods, and theCore/Extrastrict families. New dependency:zarr-metadata>=0.3.0.Testing
mypy --strict,ruff, andmkdocs build --strictall clean.pydantic_zarrnorzarr_metadata, so it's a genuine cross-check). The bare-string-form rule is derived mechanically from the metadata TypedDicts and guarded by a drift test.usage_zarr_v3.mdare executed as tests.🤖 Generated with Claude Code