Skip to content

[review slice, lands in #350] feat(zarr-metadata): one metadata field read against its definition (layer 2a) - #349

Closed
d-v-b wants to merge 1 commit into
layer/0b-must-understandfrom
layer/2a-field
Closed

d-v-b wants to merge 1 commit into
layer/0b-must-understandfrom
layer/2a-field

Conversation

@d-v-b

@d-v-b d-v-b commented Sep 22, 2026 •

Copy link
Copy Markdown
Owner

🤖 AI text below 🤖

Since this slice was cut: zarr-developers#4434 (#350) has no families. names and name_rules are gone; raw bits read as r* with the size their name carries (r16 is r* with {"bits": 16}), so claimant is a dict lookup. This slice keeps its first commit as it was, so what it says of families below is superseded.

A review slice, not a landing PR. This is 2a alone, to review the design for a single field. It lands inside #350, which carries this commit with 2b. On its own its CORE holds four definitions and would answer out_of_scope for a core data type like int8, which the guide defines as an extension nobody models. It is a draft so it cannot merge alone. Review fixes go on the upstream branch of zarr-developers#4434, which layer/2b-definitions (#350) mirrors; this branch carries the same first commit, rebuilt when that one changes.

Layer 2a of the stack, on top of #353: one metadata field read against its definition. The design for a single field, with four worked definitions; every other extension is the next PR, which interpolates this one.

The design. The type check stands alone and feeds the rules; nothing is built from a configuration to judge it. A definition is a value, not a class to subclass: the name the metadata carries, the TypedDict its configuration is (the one declaration of that JSON, which the checker from zarr-developers#4432 is compiled from), and rules, a plain function over that TypedDict yielding what the spec disallows. Its kind is its type: CodecDefinition (with the codec's kind), DataTypeDefinition, ChunkGridDefinition, ChunkKeyEncodingDefinition, StorageTransformerDefinition. Definition[C] is a generic frozen dataclass that checks itself in __post_init__; there is no __init_subclass__, metaclass or abstract base.

Three steps, each usable on its own by a caller holding only JSON:

  • check(value, SomeTypedDict): JSON against a TypedDict, no scope needed. It is zarr_metadata.typed_json.check, from feat(zarr-metadata): check, JSON against a TypedDict as the typing spec reads it zarr-developers/zarr-python#4432, re-exported here. What comes back holds what the TypedDict admits and nothing else. A member holding another metadata field is annotated with a field alias, CodecField, and checked as the JSON a metadata field is.
  • definition.judge(configuration): the check, each nested field's envelope judged (a stray member, a must_understand of false), then the rules. A rule never meets a member of the wrong type, a key its TypedDict does not declare, or a nested field whose envelope is broken, so it may index a table by key or read a nested field's name.
  • resolve(field, CodecDefinition, scope): a whole field read in a scope. The envelope is judged, the name related to a definition through Context.claimant, the configuration judged, and each nested field read the same way. It returns Resolved, with a resolution of Literal["read"] | Unread, and every problem located. A name nothing in scope claims is out_of_scope, which keeps the format open. A field is read as one of the five kinds, with or without type arguments; anything else is a TypeError. configuration_of(resolved, GZIP_CODEC) is the configuration typed as that definition's TypedDict.

What a definition refuses when it is built, each a TypeError saying what is wrong: a configuration that is not a TypedDict, says nothing of the keys it does not declare, or has a member no checker reads, named down to the TypedDict that holds it; a member typed ZarrV3MetadataFieldJSON, which would check as plain JSON and never be read as a field; a name that is not a string; rules or names that are not functions; a codec kind outside the three.

A scope holds definitions by kind: Context.of(*definitions), extended_with, claimant. It refuses a definition of no kind. A family claims many names through names and judges them through name_rules.

Worked definitions: GZIP_CODEC (a configured codec with a rule), BYTES_CODEC (an optional member), CRC32C_CODEC (nothing to configure), REGULAR_CHUNK_GRID (a tuple member with a rule). CORE holds those four; CORE_AND_EXTENSIONS equals it until 2b.

Not here yet. Nothing that needs the document or the array: no fill values against a data type, no codec transitions, no pipeline order.

Since the first version, from a design review, each with a test: a container's rule no longer raises on a nested field without a name; unknown keys no longer reach the rules or the typed value; a ZarrV3MetadataFieldJSON member, a kind nothing is filed under, and a malformed definition are refused rather than silently unread; a union's rejected branch no longer leaks nested fields; a rule's problem lands on the configuration in resolve as in judge; and resolve(field, CodecDefinition, scope) now type-checks under strict pyright, through a default on the configuration's type parameter. From the stack's design review: read_field, which nothing here called, is gone. From the second: a metadata field's own validators move to zarr_metadata.v3._common and the definitions import JSON and its problems from zarr_metadata._json, so nothing this layer adds imports the model it supports.

Also here: the door's docs page, zarr_metadata.v3.definition, whose docstring is the guide, and the public-name grammar's Definition and Field roles.

Size: about 1,700 lines over #353, 1,100 of them source; the member-naming walk and the type check itself live in the checker, merged upstream as zarr-developers#4432.

Review question for this layer: for one field, is this the right shape, and is the door's guide enough to write an extension from?

The stack: 0 model groundwork, merged upstream as zarr-developers#4420, zarr-developers#4421 and zarr-developers#4422 → 1 the checker, merged upstream as zarr-developers#4432 → 1b must_understand: false refused at every extension point (#353, upstream as zarr-developers#4433) → 2a this, landing in #350 → 2b every extension (#350, upstream as zarr-developers#4434) → 3 the field in a document → 4 the field among fields.

🤖 Generated with Claude Code

@d-v-b
d-v-b force-pushed the layer/2a-field branch 2 times, most recently from 793383a to 0200d39 Compare September 23, 2026 01:09
@d-v-b d-v-b changed the title feat(zarr-metadata): one metadata field read as an entity (layer 2a) feat(zarr-metadata): one metadata field read against its definition (layer 2a) Sep 23, 2026
d-v-b added a commit that referenced this pull request Sep 23, 2026
The JSON-first version of the one-field layer, beside #349's
dataclass-first one. The type check stands alone and feeds the rules;
nothing is built from a configuration to judge it.

A definition is a value: the name the metadata carries, the TypedDict
its configuration is -- the one declaration of that JSON, which the
checker is compiled from -- and plain functions over that TypedDict for
the rules. Its kind is its type (`CodecDefinition`, `ChunkGridDefinition`,
...), and a generic frozen dataclass checks itself in `__post_init__`:
no `__init_subclass__`, no metaclass, no abstract base. A scope holds
definitions by kind.

Three steps, each usable on its own by a caller holding JSON: `check`
(JSON against a TypedDict, no scope; a nested field, annotated
`CodecField`, is an envelope), `Definition.judge` (the check, then the
rules), and `resolve` (a whole field in a scope, returning `Resolved`
with a `Resolution` and every problem located). gzip, bytes, crc32c and
the regular grid are defined, with the verdicts #349 gives them.

1,160 lines over the checker, against #349's 2,334; `_definition.py` is
471 lines where `_entity.py` is 1,024.

Assisted-by: ClaudeCode:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@d-v-b
d-v-b marked this pull request as draft September 23, 2026 09:19
@d-v-b
d-v-b changed the base branch from layer/1-checker to layer/0b-must-understand September 23, 2026 09:51
@d-v-b d-v-b changed the title feat(zarr-metadata): one metadata field read against its definition (layer 2a) [review slice, lands in #350] feat(zarr-metadata): one metadata field read against its definition (layer 2a) Sep 23, 2026
@d-v-b
d-v-b force-pushed the layer/0b-must-understand branch from 00b51ef to 739a214 Compare September 23, 2026 10:50
@d-v-b
d-v-b force-pushed the layer/0b-must-understand branch from 739a214 to 214695c Compare September 23, 2026 11:39
@d-v-b
d-v-b force-pushed the layer/0b-must-understand branch from 214695c to 7b139f6 Compare September 26, 2026 15:19
@d-v-b
d-v-b force-pushed the layer/0b-must-understand branch from 7b139f6 to c6ca89b Compare September 26, 2026 16:16
The one-field layer, JSON first: the type check stands alone and feeds
the rules, and nothing is built from a configuration to judge it.

A definition is a value: the name the metadata carries, the TypedDict
its configuration is -- the one declaration of that JSON, which the
checker is compiled from -- and plain functions over that TypedDict for
the rules. Its kind is its type (`CodecDefinition`, `ChunkGridDefinition`,
...), and a generic frozen dataclass checks itself in `__post_init__`:
no `__init_subclass__`, no metaclass, no abstract base. A scope holds
definitions by kind.

Three steps, each usable on its own by a caller holding JSON: `check`
(JSON against a TypedDict, no scope: #347's, re-exported),
`Definition.judge` (the check, each nested field's envelope judged -- a
member annotated `CodecField` holds one -- then the rules), and
`resolve` (a whole field in a scope, returning `Resolved` with a
`Resolution` and every problem located). gzip, bytes, crc32c and the
regular grid are defined.

What the rules are handed is what the type says: a configuration holding
the keys its TypedDict admits and nothing else, whose nested fields'
envelopes are sound, so a rule may index a table by key or read a nested
field's name; a rule's problems land in the configuration in `judge` and
`resolve` alike. The checker hands nested fields back in the typed value,
so the branch of a union that did not match reads none. A definition
refuses at build what it could not read with -- a TypedDict that says
nothing of the keys it does not declare, a member typed
`ZarrV3MetadataFieldJSON`, an annotation that does not resolve, a name
that is not a string, a codec kind outside the three, rules or `names`
that are not functions -- and a field is read as one of the five kinds,
with or without type arguments, anything else a `TypeError`.
`configuration_of` types a read configuration as its definition's
TypedDict, and the configuration's type parameter has a default, so
`resolve(field, CodecDefinition, scope)` infers nothing unknown.

The definitions read JSON and its problems from `zarr_metadata._json`,
and a metadata field's own validators from `zarr_metadata.v3._common`,
where they move from the model, so nothing this layer adds imports the
model it supports.

Assisted-by: ClaudeCode:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@d-v-b

d-v-b commented Sep 27, 2026

Copy link
Copy Markdown
Owner Author

🤖 AI text below 🤖

The review slice for the single-field design, which landed inside zarr-developers#4434 (squash 8dc50b81b).

@d-v-b d-v-b closed this Sep 27, 2026
@d-v-b
d-v-b deleted the layer/2a-field branch September 27, 2026 12:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant