Part of epic #105. Depends on S1.
Problem
The §2 header is byte-pinned by the golden fixture (key order matters — §2's key-order rule), but no vpp code can build one. The sink needs to embed it in every arrow shard.
Work
views_postprocessing/unfao/wire/header.py: CONTRACT_VERSION = "1.5"; build_header(*, sample_count, dtype, target, time_id, run_id, generated_at, provenance, sharding_index, sharding_count) emitting the 13 keys in exactly the §2 pinned order; the three closed sub-objects (id_semantics, provenance, sharding) reject extra keys (§2.1 rule-1 clarification). Mirror scripts/build_wire_fixture.py:_header — do NOT import from scripts (fixture stays canonical).
Acceptance criteria
json.dumps(build_header(...), indent=2).encode() byte-equals the fixture Track-A metadata.json.
- Equals the header recovered from the fixture arrow shard (
views_frames.io.arrow.load → state['metadata']).
- Extra key in any closed sub-object raises, naming the key.
Tests: tests/test_wire_header.py. Oracle: fixture .tap.zip member + arrow shard metadata.
🤖 Generated with Claude Code
Part of epic #105. Depends on S1.
Problem
The §2 header is byte-pinned by the golden fixture (key order matters — §2's key-order rule), but no vpp code can build one. The sink needs to embed it in every arrow shard.
Work
views_postprocessing/unfao/wire/header.py:CONTRACT_VERSION = "1.5";build_header(*, sample_count, dtype, target, time_id, run_id, generated_at, provenance, sharding_index, sharding_count)emitting the 13 keys in exactly the §2 pinned order; the three closed sub-objects (id_semantics,provenance,sharding) reject extra keys (§2.1 rule-1 clarification). Mirrorscripts/build_wire_fixture.py:_header— do NOT import from scripts (fixture stays canonical).Acceptance criteria
json.dumps(build_header(...), indent=2).encode()byte-equals the fixture Track-Ametadata.json.views_frames.io.arrow.load → state['metadata']).Tests:
tests/test_wire_header.py. Oracle: fixture.tap.zipmember + arrow shard metadata.🤖 Generated with Claude Code