"""Persistent correlation relationships for the AIOS baseline. This is deliberately a relationship authority. It does not create or advance Tasks, Jobs, Releases, or Deployments. In particular, a Task is admitted only after the configured Task authority returns the real Task and its authenticated tenant/subject binding has been checked. """ from __future__ import annotations from dataclasses import asdict, dataclass, is_dataclass from datetime import datetime, timezone import json from pathlib import Path import sqlite3 from typing import Any, Callable, Mapping class LineageError(RuntimeError): """Base class for persistent lineage failures.""" class LineageConflict(LineageError): """An opaque identity was already bound to different lineage.""" class LineageVerificationError(LineageError): """An external authority did not verify the requested relationship.""" @dataclass(frozen=True) class VerifiedAcceptanceEvidence: """Typed output of a configured acceptance evidence verifier.""" acceptance_id: str tenant_id: str subject_id: str component_id: str deployment_id: str release_id: str artifact: Any accepted: bool evidence_id: str observed: Mapping[str, Any] | None = None observed_at: str | None = None def _now() -> str: return datetime.now(timezone.utc).isoformat() def _required(value: Any, name: str) -> str: text = str(value or "") if not text or len(text) > 512 or any(ord(char) < 32 for char in text): raise ValueError(f"invalid {name}") return text def _json(value: Any) -> str: return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False) _SELF_HASH_FIELDS = { "manifest_sha256", "manifest_hash", "outer_manifest_hash", "outer_manifest_sha256", "self_hash", "self_sha256", } def canonical_artifact(artifact: Any) -> str: """Return an exact artifact identity without permitting outer self-hashes.""" if not isinstance(artifact, (str, Mapping)): raise ValueError("artifact must be a string or object") if isinstance(artifact, str): _required(artifact, "artifact") else: if not artifact: raise ValueError("artifact must not be empty") if any(not isinstance(key,str) for key in artifact): raise ValueError("artifact object keys must be strings") if _SELF_HASH_FIELDS.intersection(key.lower() for key in artifact): raise ValueError("outer artifact manifest must not contain a self hash") try: return _json(artifact) except (TypeError, ValueError) as exc: raise ValueError("artifact must be canonical JSON") from exc def decode_artifact(value: str) -> Any: return json.loads(value) def _mapping(value: Any) -> Mapping[str, Any]: if isinstance(value, Mapping): return value if is_dataclass(value): return asdict(value) converter = getattr(value, "to_dict", None) if not callable(converter): converter = getattr(value, "as_dict", None) if callable(converter): try: converted = converter(redact_payload=False) except TypeError: converted = converter() if isinstance(converted, Mapping): return converted raise LineageVerificationError("task_authority returned an invalid Task") def _task_plan(task: Any) -> tuple[Mapping[str, Any], Mapping[str, Any]]: raw = _mapping(task) plan: Any = raw.get("plan") or raw.get("task_plan") if plan is None and hasattr(task, "task_plan"): plan = getattr(task, "task_plan") if plan is None: plan = raw plan_map = _mapping(plan) return raw, plan_map class LineageLedger: """SQLite-backed authority for cross-domain identity relationships.""" def __init__(self, path: str | Path, task_authority: Any, job_authority: Any): if task_authority is None: raise ValueError("task_authority is required") if job_authority is None: raise ValueError("job_authority is required") self.path = str(path) self.task_authority = task_authority self.job_authority = job_authority Path(self.path).parent.mkdir(parents=True, exist_ok=True) self._initialize() def _connect(self) -> sqlite3.Connection: connection = sqlite3.connect(self.path, timeout=30) connection.row_factory = sqlite3.Row connection.execute("PRAGMA foreign_keys=ON") connection.execute("PRAGMA busy_timeout=30000") return connection def _initialize(self) -> None: with self._connect() as connection: connection.execute("PRAGMA journal_mode=WAL") tables={row[0] for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")} if "lineage_tasks" in tables and "lineage_schema_meta" not in tables: raise LineageConflict("unversioned candidate lineage schema requires explicit migration") if "lineage_schema_meta" in tables: version=connection.execute("SELECT version FROM lineage_schema_meta WHERE domain='lineage'").fetchone() if version is None or version[0] != 2: raise LineageConflict("unsupported lineage schema version") connection.executescript( """ CREATE TABLE IF NOT EXISTS lineage_schema_meta ( domain TEXT PRIMARY KEY, version INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS lineage_tasks ( tenant_id TEXT NOT NULL, task_id TEXT NOT NULL, subject_id TEXT NOT NULL, mission_id TEXT NOT NULL, component_id TEXT NOT NULL, grant_id TEXT NOT NULL, source_task_id TEXT, metadata_json TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (tenant_id, task_id), UNIQUE (tenant_id, task_id, subject_id, mission_id, component_id, grant_id), FOREIGN KEY (tenant_id, source_task_id) REFERENCES lineage_tasks(tenant_id, task_id) ); CREATE TABLE IF NOT EXISTS lineage_effects ( tenant_id TEXT NOT NULL, effect_id TEXT NOT NULL, subject_id TEXT NOT NULL, mission_id TEXT NOT NULL, component_id TEXT NOT NULL, grant_id TEXT NOT NULL, task_id TEXT NOT NULL, job_id TEXT NOT NULL, source_effect_id TEXT, metadata_json TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (tenant_id, effect_id), UNIQUE (tenant_id, job_id), UNIQUE (tenant_id, effect_id, subject_id, mission_id, component_id), UNIQUE (tenant_id, effect_id, subject_id, mission_id, component_id, grant_id), FOREIGN KEY (tenant_id, task_id, subject_id, mission_id, component_id, grant_id) REFERENCES lineage_tasks(tenant_id, task_id, subject_id, mission_id, component_id, grant_id), FOREIGN KEY (tenant_id, source_effect_id, subject_id, mission_id, component_id) REFERENCES lineage_effects(tenant_id, effect_id, subject_id, mission_id, component_id) ); CREATE TABLE IF NOT EXISTS lineage_releases ( tenant_id TEXT NOT NULL, release_id TEXT NOT NULL, subject_id TEXT NOT NULL, mission_id TEXT NOT NULL, component_id TEXT NOT NULL, grant_id TEXT NOT NULL, effect_id TEXT NOT NULL, source_task_id TEXT NOT NULL, source_effect_id TEXT NOT NULL, artifact_json TEXT NOT NULL, metadata_json TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (tenant_id, release_id), UNIQUE (tenant_id, effect_id), UNIQUE (tenant_id, release_id, subject_id, mission_id, component_id, artifact_json), FOREIGN KEY (tenant_id, effect_id, subject_id, mission_id, component_id, grant_id) REFERENCES lineage_effects(tenant_id, effect_id, subject_id, mission_id, component_id, grant_id), FOREIGN KEY (tenant_id, source_task_id, subject_id, mission_id, component_id, grant_id) REFERENCES lineage_tasks(tenant_id, task_id, subject_id, mission_id, component_id, grant_id) ); CREATE TABLE IF NOT EXISTS lineage_deployments ( tenant_id TEXT NOT NULL, deployment_id TEXT NOT NULL, subject_id TEXT NOT NULL, mission_id TEXT NOT NULL, component_id TEXT NOT NULL, release_id TEXT NOT NULL, environment TEXT NOT NULL, target_revision INTEGER NOT NULL, artifact_json TEXT NOT NULL, metadata_json TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (tenant_id, deployment_id), UNIQUE (tenant_id, deployment_id, subject_id, mission_id, component_id, release_id, artifact_json), FOREIGN KEY (tenant_id, release_id, subject_id, mission_id, component_id, artifact_json) REFERENCES lineage_releases(tenant_id, release_id, subject_id, mission_id, component_id, artifact_json) ); CREATE TABLE IF NOT EXISTS lineage_acceptances ( tenant_id TEXT NOT NULL, acceptance_id TEXT NOT NULL, subject_id TEXT NOT NULL, mission_id TEXT NOT NULL, component_id TEXT NOT NULL, deployment_id TEXT NOT NULL, release_id TEXT NOT NULL, artifact_json TEXT NOT NULL, accepted INTEGER NOT NULL CHECK (accepted IN (0,1)), evidence_id TEXT NOT NULL, evidence_json TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (tenant_id, acceptance_id), FOREIGN KEY (tenant_id, deployment_id, subject_id, mission_id, component_id, release_id, artifact_json) REFERENCES lineage_deployments( tenant_id, deployment_id, subject_id, mission_id, component_id, release_id, artifact_json ) ); """ ) connection.execute("INSERT OR IGNORE INTO lineage_schema_meta(domain,version) VALUES('lineage',2)") def close(self) -> None: """Compatibility hook; connections are transaction-scoped.""" def _authorized_task(self, tenant_id: str, subject_id: str, task_id: str) -> Any: authority = self.task_authority try: if hasattr(authority, "get_task"): task = authority.get_task(task_id) elif callable(authority): try: task = authority(tenant_id, subject_id, task_id) except TypeError: task = authority(task_id) else: raise TypeError("authority has no task lookup") except Exception as exc: raise LineageVerificationError("task_authority did not confirm task") from exc raw, plan = _task_plan(task) returned_id = raw.get("task_id") or plan.get("task_id") or getattr(task, "task_id", None) if str(returned_id or "") != task_id: raise LineageVerificationError("task_authority returned wrong task_id") issuer = plan.get("issuer") if isinstance(plan.get("issuer"), Mapping) else {} metadata = plan.get("metadata") if isinstance(plan.get("metadata"), Mapping) else {} baseline = metadata.get("baseline") if isinstance(metadata.get("baseline"), Mapping) else {} tenant_values = [value for value in (issuer.get("tenant_id"), metadata.get("tenant_id"), baseline.get("tenant_id")) if value is not None] subject_values = [ value for value in (issuer.get("subject_id"), metadata.get("subject_id"), baseline.get("subject_id")) if value is not None ] # issuer.id is accepted as subject identity only when no explicit subject_id exists. if not subject_values and issuer.get("id") is not None: subject_values.append(issuer["id"]) if not tenant_values or any(str(value) != tenant_id for value in tenant_values): raise LineageVerificationError("task_authority tenant mismatch") if not subject_values or any(str(value) != subject_id for value in subject_values): raise LineageVerificationError("task_authority subject mismatch") return task def _authorized_job( self, tenant_id: str, subject_id: str, mission_id: str, grant_id: str, task_id: str, effect_id: str, job_id: str, ) -> None: authority=self.job_authority try: if hasattr(authority,"get_job"): job=authority.get_job(job_id) elif hasattr(authority,"read"): job=authority.read(job_id) elif callable(authority): job=authority(job_id) else: raise TypeError("authority has no Job lookup") except Exception as exc: raise LineageVerificationError("job_authority did not confirm Job") from exc raw=_mapping(job) request=raw.get("request") or raw.get("binding") if request is None and isinstance(raw.get("request_json"),str): try: request=json.loads(raw["request_json"]) except json.JSONDecodeError as exc: raise LineageVerificationError("job_authority returned invalid request") from exc if not isinstance(request,Mapping): request=raw if isinstance(request.get("binding"),Mapping): request=request["binding"] bindings={ "job_id":raw.get("job_id") or request.get("job_id"), "tenant_id":request.get("tenant_id"),"subject_id":request.get("subject_id"), "mission_id":request.get("mission_id"),"grant_id":request.get("grant_id"), "task_id":request.get("task_id"),"effect_id":request.get("effect_id"), } expected={"job_id":job_id,"tenant_id":tenant_id,"subject_id":subject_id, "mission_id":mission_id,"grant_id":grant_id,"task_id":task_id,"effect_id":effect_id} if any(str(bindings[key] or "") != value for key,value in expected.items()): raise LineageVerificationError("job_authority binding mismatch") @staticmethod def _check_task_context(task: Any, mission_id: str, grant_id: str) -> None: _, plan = _task_plan(task) metadata = plan.get("metadata") if isinstance(plan.get("metadata"), Mapping) else {} baseline = metadata.get("baseline") if isinstance(metadata.get("baseline"), Mapping) else {} mission_values = [value for value in (metadata.get("mission_id"), baseline.get("mission_id")) if value is not None] grant_values = [value for value in (metadata.get("grant_id"), baseline.get("grant_id")) if value is not None] if any(str(value) != mission_id for value in mission_values): raise LineageVerificationError("task_authority mission mismatch") if any(str(value) != grant_id for value in grant_values): raise LineageVerificationError("task_authority grant mismatch") @staticmethod def _task_component(task: Any, requested: str | None) -> str: _, plan = _task_plan(task) metadata = plan.get("metadata") if isinstance(plan.get("metadata"), Mapping) else {} baseline = metadata.get("baseline") if isinstance(metadata.get("baseline"), Mapping) else {} values=[value for value in (metadata.get("component_id"),baseline.get("component_id")) if value is not None] if requested is not None: requested=_required(requested,"component_id") if any(str(value) != requested for value in values): raise LineageVerificationError("task_authority component mismatch") return requested if not values or any(str(value) != str(values[0]) for value in values): raise LineageVerificationError("task_authority component missing or inconsistent") return _required(values[0],"component_id") def link_task( self, tenant_id: str, subject_id: str, mission_id: str, grant_id: str, task_id: str, *, component_id: str | None = None, source_task_id: str | None = None, metadata: Mapping[str, Any] | None = None, ) -> dict[str, Any]: tenant_id, subject_id, mission_id, grant_id, task_id = ( _required(tenant_id, "tenant_id"), _required(subject_id, "subject_id"), _required(mission_id, "mission_id"), _required(grant_id, "grant_id"), _required(task_id, "task_id"), ) task = self._authorized_task(tenant_id, subject_id, task_id) self._check_task_context(task, mission_id, grant_id) component_id=self._task_component(task,component_id) source_task_id = _required(source_task_id, "source_task_id") if source_task_id else None raw_task, task_plan = _task_plan(task) authoritative_source = task_plan.get("parent_task_id") or raw_task.get("parent_task_id") if authoritative_source is not None: authoritative_source = _required(authoritative_source, "authoritative source_task_id") if source_task_id is not None and source_task_id != authoritative_source: raise LineageVerificationError("source_task_id does not match Task authority") source_task_id = authoritative_source metadata_json = _json(dict(metadata or {})) values = (subject_id, mission_id, component_id, grant_id, source_task_id) with self._connect() as connection: connection.execute("BEGIN IMMEDIATE") existing = connection.execute( "SELECT * FROM lineage_tasks WHERE tenant_id=? AND task_id=?", (tenant_id, task_id) ).fetchone() if existing: if tuple(existing[key] for key in ("subject_id", "mission_id", "component_id", "grant_id", "source_task_id")) != values: raise LineageConflict("task_id already has different lineage") return self._task_row(existing) if source_task_id: self._require_row(connection, "lineage_tasks", "task_id", tenant_id, source_task_id, subject_id, mission_id, component_id) connection.execute( "INSERT INTO lineage_tasks VALUES(?,?,?,?,?,?,?,?,?)", (tenant_id, task_id, subject_id, mission_id, component_id, grant_id, source_task_id, metadata_json, _now()), ) row = connection.execute( "SELECT * FROM lineage_tasks WHERE tenant_id=? AND task_id=?", (tenant_id, task_id) ).fetchone() return self._task_row(row) @staticmethod def _require_row(connection, table, id_column, tenant_id, object_id, subject_id, mission_id, component_id=None): row = connection.execute( f"SELECT * FROM {table} WHERE tenant_id=? AND {id_column}=?", (tenant_id, object_id) ).fetchone() if row is None: raise LineageConflict(f"{id_column} has no linked authority record") if row["subject_id"] != subject_id or row["mission_id"] != mission_id: raise LineageConflict(f"{id_column} belongs to different subject or mission") if component_id is not None and row["component_id"] != component_id: raise LineageConflict(f"{id_column} belongs to different component") return row def link_effect( self, tenant_id: str, subject_id: str, mission_id: str, task_id: str, effect_id: str, job_id: str, *, source_effect_id: str | None = None, metadata: Mapping[str, Any] | None = None, ) -> dict[str, Any]: tenant_id, subject_id, mission_id, task_id, effect_id, job_id = map( lambda pair: _required(pair[0], pair[1]), ((tenant_id,"tenant_id"),(subject_id,"subject_id"),(mission_id,"mission_id"), (task_id,"task_id"),(effect_id,"effect_id"),(job_id,"job_id")), ) source_effect_id = _required(source_effect_id, "source_effect_id") if source_effect_id else None with self._connect() as connection: connection.execute("BEGIN IMMEDIATE") task=self._require_row(connection, "lineage_tasks", "task_id", tenant_id, task_id, subject_id, mission_id) component_id=task["component_id"] grant_id=task["grant_id"] self._authorized_job(tenant_id,subject_id,mission_id,grant_id,task_id,effect_id,job_id) existing = connection.execute( "SELECT * FROM lineage_effects WHERE tenant_id=? AND effect_id=?", (tenant_id, effect_id) ).fetchone() expected = (subject_id, mission_id, component_id, grant_id, task_id, job_id, source_effect_id) if existing: actual = tuple(existing[key] for key in ("subject_id","mission_id","component_id","grant_id","task_id","job_id","source_effect_id")) if actual != expected: raise LineageConflict("effect_id already has different task/job identity") return self._effect_row(existing) job = connection.execute( "SELECT effect_id,task_id,subject_id FROM lineage_effects WHERE tenant_id=? AND job_id=?", (tenant_id, job_id), ).fetchone() if job: raise LineageConflict("job_id already belongs to exactly one Task/effect") if source_effect_id: self._require_row(connection, "lineage_effects", "effect_id", tenant_id, source_effect_id, subject_id, mission_id, component_id) connection.execute( "INSERT INTO lineage_effects VALUES(?,?,?,?,?,?,?,?,?,?,?)", (tenant_id,effect_id,subject_id,mission_id,component_id,grant_id,task_id,job_id,source_effect_id,_json(dict(metadata or {})),_now()), ) row = connection.execute("SELECT * FROM lineage_effects WHERE tenant_id=? AND effect_id=?",(tenant_id,effect_id)).fetchone() return self._effect_row(row) def link_release( self, tenant_id: str, subject_id: str, mission_id: str, effect_id: str, release_id: str, artifact: Any, *, metadata: Mapping[str, Any] | None = None, ) -> dict[str, Any]: tenant_id, subject_id, mission_id, effect_id, release_id = map( lambda pair: _required(pair[0],pair[1]), ((tenant_id,"tenant_id"),(subject_id,"subject_id"),(mission_id,"mission_id"), (effect_id,"effect_id"),(release_id,"release_id")), ) artifact_json = canonical_artifact(artifact) with self._connect() as connection: connection.execute("BEGIN IMMEDIATE") effect = self._require_row(connection,"lineage_effects","effect_id",tenant_id,effect_id,subject_id,mission_id) existing = connection.execute("SELECT * FROM lineage_releases WHERE tenant_id=? AND release_id=?",(tenant_id,release_id)).fetchone() expected = (subject_id,mission_id,effect["component_id"],effect["grant_id"],effect_id,effect["task_id"],effect_id,artifact_json) if existing: actual = tuple(existing[key] for key in ("subject_id","mission_id","component_id","grant_id","effect_id","source_task_id","source_effect_id","artifact_json")) if actual != expected: raise LineageConflict("release_id already has different source lineage") return self._release_row(existing) producer = connection.execute("SELECT release_id FROM lineage_releases WHERE tenant_id=? AND effect_id=?",(tenant_id,effect_id)).fetchone() if producer: raise LineageConflict("release-producing effect already points to a unique release") connection.execute( "INSERT INTO lineage_releases VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", (tenant_id,release_id,subject_id,mission_id,effect["component_id"],effect["grant_id"],effect_id,effect["task_id"],effect_id,artifact_json,_json(dict(metadata or {})),_now()), ) row=connection.execute("SELECT * FROM lineage_releases WHERE tenant_id=? AND release_id=?",(tenant_id,release_id)).fetchone() return self._release_row(row) def link_deployment( self, tenant_id: str, subject_id: str, mission_id: str, release_id: str, deployment_id: str, component_id: str, environment: str, target_revision: int, artifact: Any, *, metadata: Mapping[str, Any] | None = None, ) -> dict[str, Any]: tenant_id,subject_id,mission_id,release_id,deployment_id,component_id,environment=map( lambda pair:_required(pair[0],pair[1]), ((tenant_id,"tenant_id"),(subject_id,"subject_id"),(mission_id,"mission_id"), (release_id,"release_id"),(deployment_id,"deployment_id"),(component_id,"component_id"),(environment,"environment")), ) if not isinstance(target_revision,int) or isinstance(target_revision,bool) or target_revision < 1: raise ValueError("invalid target_revision") artifact_json=canonical_artifact(artifact) with self._connect() as connection: connection.execute("BEGIN IMMEDIATE") release=self._require_row(connection,"lineage_releases","release_id",tenant_id,release_id,subject_id,mission_id,component_id) if release["artifact_json"] != artifact_json: raise LineageConflict("deployment artifact does not match release") existing=connection.execute("SELECT * FROM lineage_deployments WHERE tenant_id=? AND deployment_id=?",(tenant_id,deployment_id)).fetchone() expected=(subject_id,mission_id,component_id,release_id,environment,target_revision,artifact_json) if existing: actual=tuple(existing[key] for key in ("subject_id","mission_id","component_id","release_id","environment","target_revision","artifact_json")) if actual != expected: raise LineageConflict("deployment_id already has different release identity") return self._deployment_row(existing) connection.execute( "INSERT INTO lineage_deployments VALUES(?,?,?,?,?,?,?,?,?,?,?)", (tenant_id,deployment_id,subject_id,mission_id,component_id,release_id,environment,target_revision,artifact_json,_json(dict(metadata or {})),_now()), ) row=connection.execute("SELECT * FROM lineage_deployments WHERE tenant_id=? AND deployment_id=?",(tenant_id,deployment_id)).fetchone() return self._deployment_row(row) def record_acceptance( self, tenant_id: str, subject_id: str, mission_id: str, component_id: str, acceptance_id: str, deployment_id: str, release_id: str, artifact: Any, accepted: bool, *, evidence: Any, verifier: Callable[[Any, Mapping[str, Any]], VerifiedAcceptanceEvidence], ) -> dict[str, Any]: if not callable(verifier): raise LineageVerificationError("acceptance verifier is required") tenant_id,subject_id,mission_id,component_id,acceptance_id,deployment_id,release_id=map( lambda pair:_required(pair[0],pair[1]), ((tenant_id,"tenant_id"),(subject_id,"subject_id"),(mission_id,"mission_id"),(component_id,"component_id"), (acceptance_id,"acceptance_id"),(deployment_id,"deployment_id"),(release_id,"release_id")), ) if type(accepted) is not bool: raise ValueError("accepted must be boolean") artifact_json=canonical_artifact(artifact) with self._connect() as connection: connection.execute("BEGIN IMMEDIATE") deployment=self._require_row(connection,"lineage_deployments","deployment_id",tenant_id,deployment_id,subject_id,mission_id,component_id) if deployment["release_id"] != release_id: raise LineageConflict("acceptance release_id does not match deployment") if deployment["artifact_json"] != artifact_json: raise LineageConflict("acceptance artifact does not match deployment") expected={"acceptance_id":acceptance_id,"tenant_id":tenant_id,"subject_id":subject_id,"component_id":component_id, "deployment_id":deployment_id,"release_id":release_id,"artifact":decode_artifact(artifact_json), "accepted":accepted} try: verified=verifier(evidence,expected) except Exception as exc: raise LineageVerificationError("acceptance evidence was not verified") from exc if not isinstance(verified,VerifiedAcceptanceEvidence): raise LineageVerificationError("verifier must return VerifiedAcceptanceEvidence") bindings=(verified.acceptance_id,verified.tenant_id,verified.subject_id,verified.component_id,verified.deployment_id,verified.release_id,canonical_artifact(verified.artifact),verified.accepted) if bindings != (acceptance_id,tenant_id,subject_id,component_id,deployment_id,release_id,artifact_json,accepted): raise LineageVerificationError("verified acceptance evidence binding mismatch") evidence_id=_required(verified.evidence_id,"evidence_id") evidence_json=_json({ "evidence_id":evidence_id, "observed":dict(verified.observed or {}), "observed_at":verified.observed_at, "signed_envelope":dict(evidence) if isinstance(evidence,Mapping) else None, }) existing=connection.execute("SELECT * FROM lineage_acceptances WHERE tenant_id=? AND acceptance_id=?",(tenant_id,acceptance_id)).fetchone() expected_row=(subject_id,mission_id,component_id,deployment_id,release_id,artifact_json,int(accepted),evidence_id,evidence_json) if existing: actual=tuple(existing[key] for key in ("subject_id","mission_id","component_id","deployment_id","release_id","artifact_json","accepted","evidence_id","evidence_json")) if actual != expected_row: raise LineageConflict("acceptance_id already has different binding") return self._acceptance_row(existing) connection.execute( "INSERT INTO lineage_acceptances VALUES(?,?,?,?,?,?,?,?,?,?,?,?)", (tenant_id,acceptance_id,subject_id,mission_id,component_id,deployment_id,release_id,artifact_json,int(accepted),evidence_id,evidence_json,_now()), ) row=connection.execute("SELECT * FROM lineage_acceptances WHERE tenant_id=? AND acceptance_id=?",(tenant_id,acceptance_id)).fetchone() return self._acceptance_row(row) def explain(self, tenant_id: str, subject_id: str, mission_id: str) -> dict[str, Any]: tenant_id,subject_id,mission_id=(_required(tenant_id,"tenant_id"),_required(subject_id,"subject_id"),_required(mission_id,"mission_id")) specs=(("tasks","lineage_tasks",self._task_row),("effects","lineage_effects",self._effect_row), ("releases","lineage_releases",self._release_row),("deployments","lineage_deployments",self._deployment_row), ("acceptances","lineage_acceptances",self._acceptance_row)) result={"tenant_id":tenant_id,"subject_id":subject_id,"mission_id":mission_id} with self._connect() as connection: for key,table,converter in specs: rows=connection.execute(f"SELECT * FROM {table} WHERE tenant_id=? AND subject_id=? AND mission_id=? ORDER BY created_at",(tenant_id,subject_id,mission_id)).fetchall() result[key]=[converter(row) for row in rows] return result def get_release(self, tenant_id: str, subject_id: str, release_id: str) -> dict[str, Any]: """Resolve an exact tenant/subject release without disclosing other scopes.""" tenant_id, subject_id, release_id = ( _required(tenant_id, "tenant_id"), _required(subject_id, "subject_id"), _required(release_id, "release_id"), ) with self._connect() as connection: row = connection.execute( "SELECT * FROM lineage_releases WHERE tenant_id=? AND release_id=? AND subject_id=?", (tenant_id, release_id, subject_id), ).fetchone() if row is None: raise KeyError("release lineage not found") return self._release_row(row) def get_release_for_component(self, tenant_id: str, component_id: str, release_id: str) -> dict[str, Any]: tenant_id,component_id,release_id=(_required(tenant_id,"tenant_id"),_required(component_id,"component_id"),_required(release_id,"release_id")) with self._connect() as connection: row=connection.execute( "SELECT * FROM lineage_releases WHERE tenant_id=? AND release_id=? AND component_id=?", (tenant_id,release_id,component_id), ).fetchone() if row is None: raise KeyError("release component lineage not found") return self._release_row(row) @staticmethod def _common(row): return {key:row[key] for key in row.keys() if not key.endswith("_json")} @classmethod def _task_row(cls,row): value=cls._common(row); value["metadata"]=json.loads(row["metadata_json"]); return value @classmethod def _effect_row(cls,row): value=cls._common(row); value["metadata"]=json.loads(row["metadata_json"]); return value @classmethod def _release_row(cls,row): value=cls._common(row); value["artifact"]=decode_artifact(row["artifact_json"]); value["metadata"]=json.loads(row["metadata_json"]); return value @classmethod def _deployment_row(cls,row): value=cls._common(row); value["artifact"]=decode_artifact(row["artifact_json"]); value["metadata"]=json.loads(row["metadata_json"]); return value @classmethod def _acceptance_row(cls,row): value=cls._common(row); value["artifact"]=decode_artifact(row["artifact_json"]); value["accepted"]=bool(row["accepted"]); value["evidence"]=json.loads(row["evidence_json"]); return value __all__ = [ "LineageConflict", "LineageError", "LineageLedger", "LineageVerificationError", "VerifiedAcceptanceEvidence", "canonical_artifact", "decode_artifact", ]