from __future__ import annotations from collections.abc import Sequence import hashlib import json from sqlalchemy.orm import Session from govoplan_core.core.institutional import InstitutionalReference from govoplan_core.core.records import ( RecordContractError, RecordSourceLocator, RecordSourceReference, ) from govoplan_decisions.backend.db.models import FormalDecisionRevision from govoplan_decisions.backend.service import SqlDecisionRegistry CAPABILITY_RECORD_SOURCE_DECISIONS = "records.source.decisions" class DecisionsRecordSource: provider_id = "decisions" def resource_types(self) -> Sequence[str]: return ("decision_revision",) def resolve( self, session: object, principal: object, *, locator: RecordSourceLocator, purpose: str, ) -> RecordSourceReference: if not isinstance(session, Session): raise RecordContractError( "Decision record references require a database session." ) tenant_id = str(getattr(principal, "tenant_id", "") or "").strip() if not tenant_id or locator.tenant_id != tenant_id: raise RecordContractError( "Decision record references cannot cross tenants." ) if ( locator.source_module != "decisions" or locator.resource_type != "decision_revision" ): raise RecordContractError("Unsupported Decisions record source type.") if not str(purpose or "").strip(): raise RecordContractError("Decision record references require a purpose.") if not _has(principal, "decisions:decision:read"): raise RecordContractError("Current Decision read permission is required.") if not ( _has(principal, "decisions:decision:read_sensitive") or _has(principal, "decisions:decision:admin") ): raise RecordContractError( "Protected Decision read permission is required to file the complete formal outcome." ) row = ( session.query(FormalDecisionRevision) .filter( FormalDecisionRevision.tenant_id == tenant_id, FormalDecisionRevision.decision_id == locator.resource_id, FormalDecisionRevision.revision == locator.source_revision, ) .one_or_none() ) if row is None: raise RecordContractError("The exact Decision revision does not exist.") decision = SqlDecisionRegistry().get_decision( session, principal, reference=_decision_reference(row), ) if decision is None: raise RecordContractError("The exact Decision revision is unavailable.") snapshot_json = json.dumps( row.payload, sort_keys=True, separators=(",", ":"), default=str, ).encode("utf-8") return RecordSourceReference( locator=locator, label=f"Decision {decision.decision_type} - {decision.reference.object_id}", authority_mode="external_authoritative", content_sha256=hashlib.sha256(snapshot_json).hexdigest(), content_type="application/vnd.govoplan.formal-decision-revision+json", size_bytes=len(snapshot_json), valid_from=decision.temporal.valid_from, valid_to=decision.temporal.valid_to, recorded_at=decision.temporal.recorded_at, metadata={ "decision_type": decision.decision_type, "state": decision.state, "assurance_level": decision.assurance_level, "subject_count": len(decision.subject_refs), "evidence_count": len(decision.fact_evidence), "protected_snapshot": True, "snapshot_sha256": hashlib.sha256(snapshot_json).hexdigest(), }, ) def create_decisions_record_source(_context: object) -> DecisionsRecordSource: return DecisionsRecordSource() def _decision_reference(row: FormalDecisionRevision) -> InstitutionalReference: return InstitutionalReference( kind="decision", owner_module="decisions", object_id=row.decision_id, tenant_id=row.tenant_id, version=row.revision, ) def _has(principal: object, scope: str) -> bool: return bool(hasattr(principal, "has") and principal.has(scope)) __all__ = [ "CAPABILITY_RECORD_SOURCE_DECISIONS", "DecisionsRecordSource", "create_decisions_record_source", ]