"""Authorized persistence port for signal/source relationships."""
from __future__ import annotations
import sqlite3
from pathlib import Path
from .writer_policy import WriterPolicy, load_writer_policy
class SignalIntakeRepository:
"""Own the signal-to-source relationship write without owning classification."""
_FIELDS = ("signal_id", "source_name", "source_url", "url_hash", "added_at")
def __init__(self, connection: sqlite3.Connection, *, policy: WriterPolicy | None = None) -> None:
self.connection = connection
policy_path = Path(__file__).resolve().parents[1] / "architecture" / "data-ownership.yaml"
self.policy = policy or load_writer_policy(policy_path)
self.policy.require("signal_intake", "signal_sources", self._FIELDS)
self.policy.require("signal_intake", "signals", ["source_count"])
def attach_source(
self,
signal_id: str,
source_name: str,
source_url: str,
url_hash: str,
added_at: str,
) -> bool:
"""Attach one source once; return whether a new relationship was inserted."""
parent = self.connection.execute(
"SELECT 1 FROM signals WHERE signal_id = ?", (signal_id,)
).fetchone()
if parent is None:
raise ValueError(f"cannot attach source to missing signal_id: {signal_id}")
exists = self.connection.execute(
"SELECT 1 FROM signal_sources WHERE signal_id = ? AND url_hash = ?",
(signal_id, url_hash),
).fetchone()
if exists:
return False
self.connection.execute(
"INSERT INTO signal_sources (signal_id, source_name, source_url, url_hash, added_at) "
"VALUES (?, ?, ?, ?, ?)",
(signal_id, source_name, source_url, url_hash, added_at),
)
self.connection.execute(
"UPDATE signals SET source_count = COALESCE(source_count, 1) + 1 "
"WHERE signal_id = ?",
(signal_id,),
)
return True