Adds the pure-domain hash function that summarises a chunkset for
stale-detection purposes. Recorded on each DocumentStoreLink at push
time (#203 ships the column slot); compared against the recomputed
current hash to flip a link to Stale when the source has drifted.
domain/hashing.py
- chunkset_hash(chunks: Iterable[ChunkResult]) -> str
- SHA-256 over (text, source_page, headings) per chunk
- Excludes bboxes / doc_items / token_count by design
- 0x1F separator between chunks defends against the join-attack
(split A+B vs concat AB)
Tests
- 9 tests: determinism, sensitivity per included field, exclusion of
rendering-only fields, join-attack resistance, order sensitivity,
empty-input invariant
- Locked fixture: a hand-built 3-chunk input has a fixed expected hash;
CI fails loud if anyone changes the canonical inputs without updating
the fixture deliberately (and the release notes)
Service integration (recompute on chunk write, set on push) lands with
#205 once chunks are first-class — direct integration on the legacy
chunks_json path is deliberately deferred to keep #204 focused.
Refs #204