From 0fd8b1203d877a78f215bc8376e6ef8ec2583c02 Mon Sep 17 00:00:00 2001 From: BenStullsBets Date: Wed, 24 Jun 2026 07:57:07 -0700 Subject: [PATCH] =?UTF-8?q?feat(pipeline):=20strict-PD=20provenance=20reco?= =?UTF-8?q?rd=20(content=20pipeline=20=C2=A73.1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_pipeline_provenance.py | 20 ++++++++++++++++++++ tools/pipeline/provenance.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 tests/test_pipeline_provenance.py create mode 100644 tools/pipeline/provenance.py diff --git a/tests/test_pipeline_provenance.py b/tests/test_pipeline_provenance.py new file mode 100644 index 0000000..13624ce --- /dev/null +++ b/tests/test_pipeline_provenance.py @@ -0,0 +1,20 @@ +from tools.pipeline.provenance import Provenance + + +def test_provenance_to_dict_roundtrip(): + p = Provenance( + scale="abyss", + license="public-domain (US Gov, 17 U.S.C. §105)", + source="NOAA Ocean Exploration", + url="https://oceanexplorer.noaa.gov/", + fetched_at="2026-06-24", + ) + d = p.to_dict() + assert d == { + "scale": "abyss", + "license": "public-domain (US Gov, 17 U.S.C. §105)", + "source": "NOAA Ocean Exploration", + "url": "https://oceanexplorer.noaa.gov/", + "fetched_at": "2026-06-24", + } + assert Provenance.from_dict(d) == p diff --git a/tools/pipeline/provenance.py b/tools/pipeline/provenance.py new file mode 100644 index 0000000..95c00ef --- /dev/null +++ b/tools/pipeline/provenance.py @@ -0,0 +1,29 @@ +"""Stage 1 — the per-clip strict-PD provenance record (spec §3.1). + +Captured at source time so license + source + URL travel with the media. The +'no explicit license -> assume PD, verify' trap (scales design §2.1) applies: +"free stock" (Pexels/Pixabay) is NOT public domain. +""" + +from __future__ import annotations + +from dataclasses import asdict, dataclass + + +@dataclass(frozen=True) +class Provenance: + scale: str + license: str + source: str + url: str + fetched_at: str # ISO date; passed in (no clock here -> deterministic) + + def to_dict(self) -> dict: + return asdict(self) + + @classmethod + def from_dict(cls, d: dict) -> "Provenance": + return cls( + scale=d["scale"], license=d["license"], source=d["source"], + url=d["url"], fetched_at=d["fetched_at"], + )