Skip to content

refactor(registry): give each format its own files, so that two format PRs never edit the same list - #102

Merged
d-v-b merged 1 commit into
mainfrom
refactor/per-format-files
Oct 24, 2026
Merged

d-v-b merged 1 commit into
mainfrom
refactor/per-format-files

Conversation

@d-v-b

@d-v-b d-v-b commented Oct 24, 2026

Copy link
Copy Markdown
Owner

🤖 AI text below 🤖

Adding a format to refkv now touches only that format's own files, its registry file spec/virtualize/profiles/<name>.json, and the generated index pages. Before, it edited about ten shared, hand-kept lists, and every profile took a section number from one global sequence. PR #99 (MP3) and #88 (FITS) conflicted on every one of those lists and both took §18, a collision git merged silently. Outputs are unchanged.

What a format adds now

touch point before now
registry one entry in spec/virtualize/profiles.json, plus generated copies in Python, TypeScript and Rust spec/virtualize/profiles/<name>.json; the Rust core keeps per-profile copies in rust/refkv-ir/profiles/ (just profiles writes them)
Rust dispatch (run.rs, lib.rs, rules.rs, project/mod.rs) a variant, name, from_name, a sniff branch, AnyParser arms, pub mod and parser! lines, a schema static and accessor build.rs generates the module declarations, Format and the sniff order; the format module declares pub struct Profile (run::FileProfile) and calls crate::parser!; rules::of(name) reads schema/<name>.json
sniff order position in four hand-written copies (Rust, Python, TypeScript, wasm/run.mjs) sniff in the registry (ties go by name); sniffing exists only in the core (_core.sniff, vz_sniff)
browser unions and dispatch IrFormat, ImageDesc["format"] and an if chain in index.ts profiles.ts loads the registry at runtime (#profiles); the format names come from it, and the dispatch is the core's sniff
rejection tests lines in the shared tests/parsers.rs rust/refkv-ir/tests/<name>.rs (reject! is in tests/common/)
codecs a class and a docstring paragraph in zarr/codecs.py refkv/zarr/codecs/<codec>.py, imported by discovery
source-metadata schema a block in spec/generate_schemas.py spec/source_metadata/<name>.py
section numbers section in the registry, used as §N everywhere each profile document is numbered on its own (Part 2 after Part 1) and cited by its name
§1.2 signature table, harness and README prose, demo examples edited per format each profile's signature is in its own Part 2; prose that listed every format is generic or moved into docs/virtualize/<name>/README.md; demo examples are demo/examples/<name>.html

Experiment

Four branches were cut from this PR's commit. Each one added a working stub file format the new way: a parser and its Profile, a projection, a source model, a rejection test, fixtures, a spec document, a source-metadata module, a docs page and a registry file. Each then ran just profiles and uv run python spec/generate_schemas.py. Every branch's commit is 16 added files and no edited lines: 13 of the files are the format's own, and the other 3 are generated index pages that each gained one row per table.

pair names git merge after the merge
exp/a + exp/b aastub, xstub (they sort apart) clean, no conflicts index pages already current (profiles.py check); Rust core 689 passed; node --test 638 passed; spec tools, conventions and IR pytest suites passed; wasm/run.mjs sniffs and parses both new formats with no JS changes
exp/c + exp/d stuba, stubb (they sort next to each other) conflicts in the 3 generated index pages only just profiles resolves them; Rust core 689 passed

The experiment's first run turned up two places where formats far apart in name still collided: the demo's one-line formats sentence, and the sniff index, which was ordered by precedence so every new format appended at its end. The demo blocks are now filled in by js/build.mjs at build time, and the sniff index is ordered by name.

Checks

  • Rust core (cargo test --release): 687 passed, the same count before and after the tests moved into per-format files; the Python bindings crate builds (just ir-rust-test).
  • just test (pytest, with the Rust core built): passed.
  • just js::test: 638 passed.
  • just sections: passed (spec tools tests, the section check, the profiles check).
  • just cli-identical: 4550 conversions, 4550 identical, 0 differ.
  • spec/generate_schemas.py writes the same 17 schemas byte for byte; js/build.mjs builds and inlines the registry.

What is left

  • Index pages can still conflict. The tables in spec/virtualize.md, spec/conventions.md and docs/virtualize/README.md, and the demo's generated blocks, list every profile and stay committed. When two new names sort next to each other, both branches insert at the same spot, and running just profiles resolves it. The experiment above shows this case.
  • Store formats (StoreFormat in store.rs, the §1.4 root-key table, JS chooseProfile) still dispatch by hand. No format PR in flight adds one.
  • Format-specific bindings (fits_tiles_decode and _set_chunk_frames in python/src/lib.rs, vz_fits_tiles_decode in wasm.rs) and the two per-profile hooks in mirror.rs stay in shared files. A format that needs neither touches neither.
  • Dependencies: a new extra or dev dependency still edits pyproject.toml and uv.lock.
  • just ir-wasm: now reports the VSI sample as rejected. Its file run only rejects, and wasm/run.mjs used to read the file as a TIFF.
Notes for review
  • Spec references were rewritten by a one-off script over every tracked file except spec/history/, frozen reference code included (comments only). just sections passes.
  • The frozen-reference lists in HARNESS.md and compare.py (the formats that had a reference) are historical and fixed, so they stay as they are.
  • refkv.virtualize.profiles now reads the registry from refkv._core, so spec/generate_schemas.py needs a core built from the same tree (uv run rebuilds it, since the cache keys include rust/refkv-ir/profiles/*.json and build.rs).

🤖 Generated with Claude Code

…t PRs never edit the same list

Adding a format edited about ten shared, hand-kept lists, all at the same spot, and every
profile took a number from one global sequence. Each of those now derives from the format's
own files and its registry entry:

- The registry is one file per profile, spec/virtualize/profiles/<name>.json, with an
  explicit `sniff` precedence instead of list position; the Rust core keeps per-profile copies
  in rust/refkv-ir/profiles/.
- Rust dispatch: build.rs generates the format modules' declarations (lib.rs,
  project/mod.rs), the `Format` enum and the sniff order from the copies, and the source models'
  table from schema/; each format module declares a `Profile` (`run::FileProfile`: signature,
  parser, response mode, projection, reads, post-mirror step) and invokes `parser!` itself.
  `rules::of(name)` replaces the per-format schema statics and accessors.
- Sniffing exists once, in the core: Python (`_core.sniff`), the browser (`vz_sniff`) and
  wasm/run.mjs call it instead of keeping their own copies.
- Browser: profiles.ts loads the registry at runtime (`#profiles`: read from the spec under
  Node, inlined by build.mjs into the bundles); `IrFormat` and the image format are its names,
  and the file dispatch is the core's sniff. Python reads the registry from the core.
- Rejection tests moved from tests/parsers.rs into tests/<format>.rs (tests/common/ holds
  `reject!`); 687 tests before and after.
- Codecs: refkv.zarr.codecs is a package of one module per codec, imported by discovery.
- Schemas: spec/generate_schemas.py loads each profile's source-metadata schema from
  spec/source_metadata/<name>.py; the generated schemas are unchanged.
- Spec numbering: each profile document is numbered on its own (Part 2 after Part 1) and
  cited by name; every reference was rewritten. The §1.2 signature table moved into each
  profile's document, and spec/virtualize.md states the rule.
- Shared prose that enumerated formats (HARNESS.md, compare.py, js/README.md, the justfiles,
  the demo's examples and credits) is generic or moved into each format's page; the demo's
  examples are demo/examples/<name>.html, and fixture generators opt out of `just fixtures`
  with a `# fixtures: on demand` line.

The index pages (spec/virtualize.md's, spec/conventions.md's and docs/virtualize/README.md's
tables, and the demo page's blocks) stay generated and committed by `just profiles`: they are
the one place two format branches can still conflict, when their names sort next to each
other, and regenerating resolves it. Outputs are unchanged.

Assisted-by: ClaudeCode:claude-opus-5-5
@d-v-b
d-v-b merged commit 011d0f8 into main Oct 24, 2026
51 checks passed
@d-v-b
d-v-b deleted the refactor/per-format-files branch October 24, 2026 10:50
d-v-b added a commit that referenced this pull request Oct 24, 2026
…jection test in tests/ndpi.rs

Merges origin/main (the per-format registry refactor, #102). Its compare.py and
fixture generator comments are kept with REVISED's; the NDPI profile is now §7 of
spec/virtualize/ndpi.md, so the links this branch added point there; and the rejected
level-0 frame fixture gets its reject! case in the new tests/ndpi.rs.

Assisted-by: ClaudeCode:claude-opus-5-5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d-v-b added a commit that referenced this pull request Oct 24, 2026
…register MP4 and refkv.stream the new way

main gave each format its own files. MP4 now registers through them: the registry file
spec/virtualize/profiles/mp4.json (sniff precedence 85, before MRC's), a Profile and
parser! in src/mp4.rs, rules::of("mp4"), tests/mp4.rs, spec/source_metadata/mp4.py; the
refkv.stream codec is the module refkv.zarr.codecs.stream, in place of main's mp3 module;
the MP3 and MP4 documents number their profile part on their own (§8) and are cited by name.

Assisted-by: ClaudeCode:claude-opus-5-5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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