307 lines
11 KiB
Python
307 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""Prove the governed Connectors -> Risk Compliance sanctions journey."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from types import SimpleNamespace
|
|
|
|
from sqlalchemy import create_engine
|
|
from sqlalchemy.orm import Session
|
|
|
|
from govoplan_connectors.backend.db.models import (
|
|
ConnectorSanctionsAcquisitionRun,
|
|
ConnectorSanctionsSnapshot,
|
|
)
|
|
from govoplan_connectors.backend.sanctions_sources import (
|
|
SANCTIONS_READ_SCOPE as CONNECTOR_SANCTIONS_READ_SCOPE,
|
|
SANCTIONS_REFRESH_SCOPE,
|
|
SYNTHETIC_PROVIDER_ID,
|
|
)
|
|
from govoplan_core.auth import ApiPrincipal
|
|
from govoplan_core.core.access import PrincipalRef
|
|
from govoplan_core.core.modules import ModuleContext
|
|
from govoplan_core.core.recovery import RecoveryCheckpoint, RecoveryOperation
|
|
from govoplan_core.core.runtime_coordination import (
|
|
DistributedLease,
|
|
RuntimeIdentity,
|
|
bind_process_runtime_identity,
|
|
)
|
|
from govoplan_core.core.sanctions import (
|
|
SanctionsScreeningFreshnessRequest,
|
|
SanctionsScreeningPolicy,
|
|
SanctionsScreeningRequest,
|
|
SanctionsScreeningSubject,
|
|
sanctions_screening_provider,
|
|
sanctions_snapshot_provider,
|
|
)
|
|
from govoplan_core.db.base import Base
|
|
from govoplan_core.server.registry import build_platform_registry
|
|
from govoplan_risk_compliance.backend.db.models import (
|
|
RiskAssuranceEdge,
|
|
RiskAssuranceNode,
|
|
RiskSanctionsAddress,
|
|
RiskSanctionsAlias,
|
|
RiskSanctionsDate,
|
|
RiskSanctionsEntry,
|
|
RiskSanctionsIdentifier,
|
|
RiskSanctionsListSnapshot,
|
|
RiskScreeningCandidate,
|
|
RiskScreeningDisposition,
|
|
RiskScreeningException,
|
|
RiskScreeningRun,
|
|
RiskScreeningSubjectSnapshot,
|
|
)
|
|
from govoplan_risk_compliance.backend.permissions import (
|
|
SANCTIONS_ADMIN_SCOPE,
|
|
SANCTIONS_READ_SCOPE,
|
|
SANCTIONS_REVIEW_SCOPE,
|
|
SANCTIONS_SCREEN_SCOPE,
|
|
)
|
|
from govoplan_risk_compliance.backend.review import (
|
|
DispositionInput,
|
|
record_disposition,
|
|
)
|
|
from govoplan_risk_compliance.backend.sanctions_catalog import (
|
|
import_connector_snapshot,
|
|
)
|
|
from govoplan_risk_compliance.backend.screening import (
|
|
get_screening_run,
|
|
list_rescreening_requirements,
|
|
)
|
|
|
|
|
|
TABLES = (
|
|
DistributedLease.__table__,
|
|
RecoveryOperation.__table__,
|
|
RecoveryCheckpoint.__table__,
|
|
ConnectorSanctionsAcquisitionRun.__table__,
|
|
ConnectorSanctionsSnapshot.__table__,
|
|
RiskAssuranceNode.__table__,
|
|
RiskAssuranceEdge.__table__,
|
|
RiskSanctionsListSnapshot.__table__,
|
|
RiskSanctionsEntry.__table__,
|
|
RiskSanctionsAlias.__table__,
|
|
RiskSanctionsIdentifier.__table__,
|
|
RiskSanctionsDate.__table__,
|
|
RiskSanctionsAddress.__table__,
|
|
RiskScreeningSubjectSnapshot.__table__,
|
|
RiskScreeningRun.__table__,
|
|
RiskScreeningCandidate.__table__,
|
|
RiskScreeningDisposition.__table__,
|
|
RiskScreeningException.__table__,
|
|
)
|
|
|
|
|
|
def main() -> int:
|
|
registry = build_platform_registry(("connectors", "risk_compliance"))
|
|
registry.configure_capability_context(
|
|
ModuleContext(registry=registry, settings=object())
|
|
)
|
|
snapshot_provider = sanctions_snapshot_provider(registry)
|
|
screening_provider = sanctions_screening_provider(registry)
|
|
refresh_source = getattr(snapshot_provider, "refresh_source", None)
|
|
if snapshot_provider is None or not callable(refresh_source):
|
|
raise RuntimeError("Connectors sanctions acquisition is unavailable.")
|
|
if screening_provider is None:
|
|
raise RuntimeError("Risk Compliance sanctions screening is unavailable.")
|
|
|
|
engine = create_engine("sqlite:///:memory:")
|
|
Base.metadata.create_all(engine, tables=TABLES)
|
|
bind_process_runtime_identity(
|
|
RuntimeIdentity(
|
|
installation_id="sanctions-composition-check",
|
|
node_id="sanctions-worker",
|
|
incarnation="sanctions-worker-incarnation",
|
|
role="worker",
|
|
software_version="test",
|
|
composition_hash="d" * 64,
|
|
)
|
|
)
|
|
try:
|
|
with Session(engine) as session:
|
|
operator = _principal("operator-1", operational=True)
|
|
reviewer = _principal("reviewer-1", operational=False)
|
|
|
|
acquired = refresh_source(
|
|
session,
|
|
operator,
|
|
provider_id=SYNTHETIC_PROVIDER_ID,
|
|
idempotency_key="synthetic-sanctions-2026-08-01",
|
|
)
|
|
replay = refresh_source(
|
|
session,
|
|
operator,
|
|
provider_id=SYNTHETIC_PROVIDER_ID,
|
|
idempotency_key="synthetic-sanctions-2026-08-01",
|
|
)
|
|
if acquired.status != "succeeded" or acquired.snapshot is None:
|
|
raise RuntimeError(f"Synthetic acquisition failed: {acquired!r}")
|
|
if (
|
|
replay.run_id != acquired.run_id
|
|
or replay.snapshot is None
|
|
or replay.snapshot.ref != acquired.snapshot.ref
|
|
or replay.snapshot.sha256 != acquired.snapshot.sha256
|
|
):
|
|
raise RuntimeError("Acquisition idempotency did not replay exact evidence.")
|
|
|
|
imported, created = import_connector_snapshot(
|
|
session,
|
|
operator,
|
|
registry=registry,
|
|
connector_snapshot_ref=acquired.snapshot.ref,
|
|
)
|
|
imported_replay, replay_created = import_connector_snapshot(
|
|
session,
|
|
operator,
|
|
registry=registry,
|
|
connector_snapshot_ref=acquired.snapshot.ref,
|
|
)
|
|
if not created or replay_created or imported_replay.id != imported.id:
|
|
raise RuntimeError("Risk Compliance snapshot import is not idempotent.")
|
|
|
|
subject = SanctionsScreeningSubject(
|
|
subject_type="person",
|
|
primary_name="Alex Example",
|
|
subject_ref="party:fixture-person-1",
|
|
)
|
|
policy = SanctionsScreeningPolicy(failure_policy="block")
|
|
request = SanctionsScreeningRequest(
|
|
list_snapshot_id=imported.id,
|
|
idempotency_key="fixture-party-screening-1",
|
|
subject=subject,
|
|
policy=policy,
|
|
)
|
|
screened = screening_provider.request_screening(
|
|
session,
|
|
operator,
|
|
request,
|
|
)
|
|
screened_replay = screening_provider.request_screening(
|
|
session,
|
|
operator,
|
|
request,
|
|
)
|
|
if not screened.created or screened_replay.created:
|
|
raise RuntimeError("Screening request idempotency is not stable.")
|
|
if screened.evidence.ref != screened_replay.evidence.ref:
|
|
raise RuntimeError("Screening replay returned different evidence.")
|
|
if screened.evidence.outcome != "potential" or screened.evidence.candidate_count != 1:
|
|
raise RuntimeError(f"Synthetic match was not reviewable: {screened.evidence!r}")
|
|
|
|
run = get_screening_run(
|
|
session,
|
|
operator,
|
|
run_id=screened.evidence.run_id,
|
|
)
|
|
candidate, disposition = record_disposition(
|
|
session,
|
|
reviewer,
|
|
candidate_id=run.candidates[0].id,
|
|
disposition=DispositionInput(
|
|
decision="false_positive",
|
|
reason="Independent fixture evidence excludes the screened party.",
|
|
evidence_refs=(acquired.snapshot.raw_evidence_ref,),
|
|
),
|
|
)
|
|
if candidate.review_status != "false_positive":
|
|
raise RuntimeError("Independent review did not resolve the candidate.")
|
|
if disposition.separation_status != "independent":
|
|
raise RuntimeError("Reviewer separation evidence was not retained.")
|
|
|
|
cleared = screening_provider.check_freshness(
|
|
session,
|
|
operator,
|
|
SanctionsScreeningFreshnessRequest(
|
|
evidence_ref=screened.evidence.ref,
|
|
current_subject=subject,
|
|
expected_list_snapshot_id=imported.id,
|
|
policy=policy,
|
|
),
|
|
)
|
|
if not cleared.fresh or cleared.gate_decision != "allow":
|
|
raise RuntimeError(f"Reviewed evidence did not clear the gate: {cleared!r}")
|
|
|
|
# Acquisition and review are separate durable commands in production.
|
|
session.commit()
|
|
session.expire_all()
|
|
|
|
refreshed = refresh_source(
|
|
session,
|
|
operator,
|
|
provider_id=SYNTHETIC_PROVIDER_ID,
|
|
idempotency_key="synthetic-sanctions-2026-08-02",
|
|
)
|
|
if refreshed.snapshot is None or refreshed.snapshot.ref == acquired.snapshot.ref:
|
|
raise RuntimeError("A new acquisition did not create new immutable evidence.")
|
|
current, current_created = import_connector_snapshot(
|
|
session,
|
|
operator,
|
|
registry=registry,
|
|
connector_snapshot_ref=refreshed.snapshot.ref,
|
|
)
|
|
if not current_created:
|
|
raise RuntimeError("The refreshed list state was not imported separately.")
|
|
|
|
stale = screening_provider.check_freshness(
|
|
session,
|
|
operator,
|
|
SanctionsScreeningFreshnessRequest(
|
|
evidence_ref=screened.evidence.ref,
|
|
current_subject=subject,
|
|
expected_list_snapshot_id=current.id,
|
|
policy=policy,
|
|
),
|
|
)
|
|
if stale.fresh or stale.gate_decision != "block":
|
|
raise RuntimeError(f"Changed source evidence did not close the gate: {stale!r}")
|
|
if "source_snapshot_changed" not in stale.reasons:
|
|
raise RuntimeError("Source change provenance was not reported.")
|
|
requirements = list_rescreening_requirements(session, operator)
|
|
if screened.evidence.run_id not in {item.run.id for item in requirements}:
|
|
raise RuntimeError("The stale screening is absent from the rescreening queue.")
|
|
|
|
session.commit()
|
|
if session.query(RecoveryOperation).count() != 2:
|
|
raise RuntimeError("Connector acquisition recovery evidence is incomplete.")
|
|
if session.query(RiskScreeningDisposition).count() != 1:
|
|
raise RuntimeError("Disposition evidence was duplicated or lost.")
|
|
finally:
|
|
bind_process_runtime_identity(None)
|
|
engine.dispose()
|
|
|
|
print(
|
|
"Connectors -> immutable sanctions snapshot -> Risk Compliance review "
|
|
"and rescreening composition passed."
|
|
)
|
|
return 0
|
|
|
|
|
|
def _principal(account_id: str, *, operational: bool) -> ApiPrincipal:
|
|
scopes = {
|
|
SANCTIONS_READ_SCOPE,
|
|
SANCTIONS_REVIEW_SCOPE,
|
|
}
|
|
if operational:
|
|
scopes.update(
|
|
{
|
|
CONNECTOR_SANCTIONS_READ_SCOPE,
|
|
SANCTIONS_REFRESH_SCOPE,
|
|
SANCTIONS_ADMIN_SCOPE,
|
|
SANCTIONS_SCREEN_SCOPE,
|
|
}
|
|
)
|
|
return ApiPrincipal(
|
|
principal=PrincipalRef(
|
|
account_id=account_id,
|
|
membership_id=f"membership-{account_id}",
|
|
tenant_id="tenant-1",
|
|
scopes=frozenset(scopes),
|
|
),
|
|
account=SimpleNamespace(id=account_id),
|
|
user=SimpleNamespace(id=f"membership-{account_id}"),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|