Coverage for narrative_harm_classifier/classifier/provenance.py: 100%
10 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-20 13:25 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-20 13:25 +0000
1"""
2classifier/provenance.py — Deterministic content hashing and a tamper-evident
3hash chain for escalation-tracking observations.
5content_hash() is a pure function of (text, context, taxonomy_version): the
6same input always produces the same hash regardless of when it's computed,
7so it can be used to verify "this exact text was classified under this
8exact taxonomy version" independent of any particular run.
10record_hash() chains each Observation to the one before it (genesis
11GENESIS_HASH for the first record in a source's history), the same idea as
12a git commit chain or a minimal Merkle-style ledger: altering any historical
13record changes its hash, which no longer matches what every later record's
14hash was computed from — making tampering detectable, not prevented.
15"""
17import hashlib
18from datetime import datetime
19from typing import Optional
21GENESIS_HASH = "0" * 64
24def content_hash(text: str, context: Optional[str], taxonomy_version: str) -> str:
25 payload = f"{text}\x1f{context or ''}\x1f{taxonomy_version}"
26 return hashlib.sha256(payload.encode("utf-8")).hexdigest()
29def record_hash(
30 prev_hash: str,
31 source_id: str,
32 text_excerpt: str,
33 is_harmful: bool,
34 harm_mechanism: Optional[str],
35 confidence: float,
36 observed_at: datetime,
37 content_hash_value: str,
38) -> str:
39 payload = "\x1f".join(
40 [
41 prev_hash,
42 source_id,
43 text_excerpt,
44 str(is_harmful),
45 harm_mechanism or "",
46 f"{confidence:.6f}",
47 observed_at.isoformat(),
48 content_hash_value,
49 ]
50 )
51 return hashlib.sha256(payload.encode("utf-8")).hexdigest()