From 03f53393da830b6f0eb0a914e643d15da8d162e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen?= Date: Sun, 29 Mar 2026 13:24:09 +0200 Subject: [PATCH 1/3] Publish API container to GHCR --- .github/workflows/ci.yml | 8 ++-- .github/workflows/package.yml | 74 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 6 +-- CHANGELOG.md | 9 +++++ Dockerfile | 4 ++ README.md | 21 +++++++++- 6 files changed, 113 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/package.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6e85a4..5756850 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,8 +11,8 @@ jobs: name: Lint (ruff) runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: "3.12" - run: pip install ruff @@ -25,8 +25,8 @@ jobs: matrix: python-version: ["3.11", "3.12"] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - run: pip install -r requirements.txt pytest pytest-asyncio httpx diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml new file mode 100644 index 0000000..4c77b4f --- /dev/null +++ b/.github/workflows/package.yml @@ -0,0 +1,74 @@ +name: Package + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag_name: + description: "Existing release tag to package, e.g. v0.1.0-alpha.1" + required: true + type: string + +permissions: + contents: read + packages: write + +env: + IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/beyond-ai-api + +jobs: + publish_api_image: + name: Publish API image + runs-on: ubuntu-latest + env: + IMAGE_VERSION: ${{ github.event_name == 'workflow_dispatch' && inputs.tag_name || github.ref_name }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + ref: ${{ env.IMAGE_VERSION }} + + - name: Validate requested tag + run: git describe --tags --exact-match >/dev/null + + - uses: actions/setup-python@v6 + with: + python-version: "3.12" + + - name: Lint and test before publishing + run: | + pip install -r requirements.txt pytest pytest-asyncio httpx ruff + ruff check . + pytest sanctions/tests/ -q + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ github.token }} + + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE_NAME }} + tags: | + type=raw,value=${{ env.IMAGE_VERSION }} + type=semver,pattern={{version}},value=${{ env.IMAGE_VERSION }} + type=raw,value=latest,enable=${{ !contains(env.IMAGE_VERSION, '-') }} + labels: | + org.opencontainers.image.title=Beyond AI API + org.opencontainers.image.description=Beyond AI Sanctions Screening API + + - uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + push: true + build-args: | + BEYOND_AI_VERSION=${{ env.IMAGE_VERSION }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 137118b..ff892dd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,8 +16,8 @@ jobs: matrix: python-version: ["3.11", "3.12"] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v6 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - run: pip install -r requirements.txt pytest pytest-asyncio httpx ruff @@ -31,7 +31,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 0 - name: Create GitHub release from tag diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb273d..2eb77de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ Alle nennenswerten Aenderungen an diesem Projekt werden in dieser Datei festgeha ## [Unreleased] +### Added + +- GHCR-Publish-Workflow fuer das API-Container-Image bei Release-Tags und manuellen Tag-Backfills. +- Build-Argument fuer `BEYOND_AI_VERSION`, damit Container und Health-Endpoint dieselbe Release-Version tragen. + +### Changed + +- GitHub-Actions-Workflows auf Node-24-faehige Major-Versionen von `actions/checkout` und `actions/setup-python` angehoben. + ## [0.1.0-alpha.1] - 2026-03-29 ### Added diff --git a/Dockerfile b/Dockerfile index 5e596b3..acad3ac 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,11 @@ FROM python:3.12-slim +ARG BEYOND_AI_VERSION=dev + WORKDIR /app +ENV BEYOND_AI_VERSION=${BEYOND_AI_VERSION} + COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt diff --git a/README.md b/README.md index 58cd63a..9ba08a1 100644 --- a/README.md +++ b/README.md @@ -72,14 +72,31 @@ curl -X POST http://localhost:8000/api/screen \ ## Releases -Beyond AI veroeffentlicht aktuell bewusst **GitHub Releases fuer Source + Docker**, noch keine installierbaren Packages. +Beyond AI veroeffentlicht aktuell bewusst **GitHub Releases fuer Source + Docker**. Paketiert wird vorerst nur die API als GHCR-Container, noch nicht als installierbares Python-Package. - Tags im Format `vX.Y.Z-alpha.N`, `vX.Y.Z-beta.N` oder `vX.Y.Z-rc.N` werden als Pre-Releases veroeffentlicht. - Tags im Format `vX.Y.Z` werden als stabile Releases veroeffentlicht. - Jeder Push eines passenden Tags startet den Release-Workflow und erstellt automatisch den GitHub Release. - Der aktuelle Release-Verlauf steht in [CHANGELOG.md](CHANGELOG.md). -Das erste oeffentliche Release ist als `v0.1.0-alpha.1` vorgesehen: fruehes Nutzerfeedback, reproduzierbarer Quellstand, aber noch kein Packaging fuer PyPI oder GitHub Packages. +Das erste oeffentliche Release `v0.1.0-alpha.1` dient bewusst dem fruehen Nutzerfeedback: reproduzierbarer Quellstand, veroeffentlichter Docker-Stack und optionales GHCR-Container-Package, aber noch kein Packaging fuer PyPI. + +## Packages + +Das API-Image wird ueber GitHub Container Registry als `ghcr.io/endvater/beyond-ai-api` veroeffentlicht. + +- Release-Tags publizieren ein Image mit exakt demselben Tag, z. B. `v0.1.0-alpha.1`. +- Zusaetzlich wird ein SemVer-Tag ohne fuehrendes `v` publiziert, z. B. `0.1.0-alpha.1`. +- Nur stabile Releases ohne Suffix publizieren ausserdem `latest`. +- Bereits existierende Release-Tags lassen sich ueber den `Package`-Workflow per `workflow_dispatch` nachziehen. + +```bash +docker pull ghcr.io/endvater/beyond-ai-api:v0.1.0-alpha.1 + +docker run --rm -p 8000:8000 \ + -e YENTE_URL=http://host.docker.internal:8100 \ + ghcr.io/endvater/beyond-ai-api:v0.1.0-alpha.1 +``` ## Voraussetzungen From 59bf337d84f1c9527ed89351fa20ffcda578141b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ju=CC=88rgen?= Date: Sun, 29 Mar 2026 13:38:07 +0200 Subject: [PATCH 2/3] Grant provenance permissions to package workflow --- .github/workflows/package.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 4c77b4f..66ec85f 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -14,6 +14,8 @@ on: permissions: contents: read packages: write + attestations: write + id-token: write env: IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/beyond-ai-api @@ -68,6 +70,7 @@ jobs: context: . file: ./Dockerfile push: true + provenance: true build-args: | BEYOND_AI_VERSION=${{ env.IMAGE_VERSION }} tags: ${{ steps.meta.outputs.tags }} From 03a29cf894860379d4c4af42856b84596a2b143a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Schiller=20Garc=C3=ADa?= <31987479+endvater@users.noreply.github.com> Date: Sun, 5 Apr 2026 14:41:29 +0200 Subject: [PATCH 3/3] Add observability and federation architecture layers (#12) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jürgen --- CHANGELOG.md | 5 ++ README.md | 125 +++++++++++++++++++++++------- federation/README.md | 69 +++++++++++++++++ federation/__init__.py | 1 + observability/README.md | 78 +++++++++++++++++++ observability/__init__.py | 1 + pyproject.toml | 2 +- shared/confidence/__init__.py | 35 +++++++++ shared/federation/__init__.py | 17 ++++ shared/federation/models.py | 95 +++++++++++++++++++++++ shared/observability/__init__.py | 19 +++++ shared/observability/models.py | 107 +++++++++++++++++++++++++ tests/test_architecture_models.py | 104 +++++++++++++++++++++++++ 13 files changed, 630 insertions(+), 28 deletions(-) create mode 100644 federation/README.md create mode 100644 federation/__init__.py create mode 100644 observability/README.md create mode 100644 observability/__init__.py create mode 100644 shared/federation/__init__.py create mode 100644 shared/federation/models.py create mode 100644 shared/observability/__init__.py create mode 100644 shared/observability/models.py create mode 100644 tests/test_architecture_models.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 2eb77de..120e244 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,15 @@ Alle nennenswerten Aenderungen an diesem Projekt werden in dieser Datei festgeha - GHCR-Publish-Workflow fuer das API-Container-Image bei Release-Tags und manuellen Tag-Backfills. - Build-Argument fuer `BEYOND_AI_VERSION`, damit Container und Health-Endpoint dieselbe Release-Version tragen. +- Neues Architekturmodul `observability/` fuer den Qualitaets-Layer aus Detection Integrity, Data Trust, Service Health und Business Impact. +- Neues Architekturmodul `federation/` fuer das foederative Drei-Schichtenmodell aus Public, Federated und Internal Layer. +- Shared-Modelle fuer Confidence Levels, Surrogat-Korrelation, Quality Signals und federative Sichtbarkeitsklassen. +- Tests fuer die neuen Shared-Modelle und ihre Routing-/Impact-Logik. ### Changed - GitHub-Actions-Workflows auf Node-24-faehige Major-Versionen von `actions/checkout` und `actions/setup-python` angehoben. +- Root-README um Observability-, Federation- und Strangler-Prinzipien aus der Watchdog-Serie erweitert. ## [0.1.0-alpha.1] - 2026-03-29 diff --git a/README.md b/README.md index 9ba08a1..f8afea7 100644 --- a/README.md +++ b/README.md @@ -21,39 +21,103 @@ Dieses Repository ist der Beweis, dass die Technologie reif ist β€” und dass der | [`sanctions/`](sanctions) | πŸ”΄ Sprint 1 | Sanctions & PEP Screening auf Basis von OpenSanctions + LLM Name Matching | Dow Jones R&C, World-Check, Sanction Scanner | | [`horizon/`](horizon) | βšͺ Geplant | Regulatory Horizon Scanner β€” EUR-Lex, BaFin, EBA/ESMA automatisch gescrapt und LLM-klassifiziert | VΓ–B RADAR, CUBE, msg LCM | | [`osint/`](osint) | βšͺ Geplant | Adverse Media & OSINT β€” RSS-Aggregation, LLM-Klassifikation, Entity Resolution | LexisNexis, Quantexa, Chainalysis | -| [`shared/`](shared) | 🟑 Basis | Gemeinsame Infrastruktur: Neo4j-Connector, LLM-Gateway, Confidence Framework, Config | β€” | +| [`observability/`](observability) | 🟑 Blueprint | Qualitaets-Layer fuer Detection Integrity, Data Trust, Service Health und Business Impact | isolierte Monitoring-Kacheln, fachlich blinde DQ-Programme | +| [`federation/`](federation) | 🟑 Blueprint | Foederatives Schichtenmodell: oeffentlicher, foederativer und bankinterner Layer | Single-Bank-Silos, Vendor-Blackboxes | +| [`shared/`](shared) | 🟑 Basis | Gemeinsame Infrastruktur: Neo4j-Connector, LLM-Gateway, Confidence, Observability- und Federation-Modelle | β€” | ## Architektur ``` -β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” -β”‚ Beyond AI β”‚ -β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ sanctions/ β”‚ β”‚ horizon/ β”‚ β”‚ osint/ β”‚ β”‚ -β”‚ β”‚ Screening β”‚ β”‚ Scanner β”‚ β”‚ Adverse Media β”‚ β”‚ -β”‚ β”‚ PEP/Lists β”‚ β”‚ Norms β”‚ β”‚ Entity Resolution β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ β”‚ β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ shared/ β”‚ β”‚ -β”‚ β”‚ Neo4j Β· LLM Gateway (Ollama/Claude) Β· Confidence β”‚ β”‚ -β”‚ β”‚ Config Β· FastAPI Boilerplate Β· Auth β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β”‚ β”‚ β”‚ -β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ -β”‚ β”‚ Datenquellen (ΓΆffentlich) β”‚ β”‚ -β”‚ β”‚ OpenSanctions Β· EUR-Lex Β· BaFin Β· EBA Β· OFAC Β· UN β”‚ β”‚ -β”‚ β”‚ ICIJ Β· OpenCorporates Β· Etherscan Β· Wikidata β”‚ β”‚ -β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ -β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ - β”‚ - β–Ό - FinRegAgents (upstream) - github.com/endvater/finreg-agents - Confidence-aware Validation Framework +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Beyond AI β”‚ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ sanctions/ β”‚ β”‚ horizon/ β”‚ β”‚ osint/ β”‚ β”‚ +β”‚ β”‚ Screening β”‚ β”‚ Scanner β”‚ β”‚ Adverse Media β”‚ β”‚ +β”‚ β”‚ PEP/Lists β”‚ β”‚ Norms β”‚ β”‚ Entity Resolution β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ observability/ β”‚ β”‚ +β”‚ β”‚ Detection Integrity Β· Data Trust Β· Service Health Β· Impact Map β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ federation/ β”‚ β”‚ +β”‚ β”‚ Public Layer Β· Federated Layer Β· Internal Layer β”‚ β”‚ +β”‚ β”‚ Confidence-aware Sidecars Β· Privacy-preserving Signals β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ shared/ β”‚ β”‚ +β”‚ β”‚ Neo4j Β· LLM Gateway Β· Confidence Β· Observability Models β”‚ β”‚ +β”‚ β”‚ Federation Models Β· Config Β· FastAPI Boilerplate β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Datenquellen und Signale β”‚ β”‚ +β”‚ β”‚ Public Data Β· Federated Signals Β· Internal Telemetry β”‚ β”‚ +β”‚ β”‚ OpenSanctions Β· EUR-Lex Β· BaFin Β· EBA Β· ICIJ Β· OpenCorporates β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β–Ό + FinRegAgents (upstream) + github.com/endvater/finreg-agents + Confidence-aware Validation Framework ``` +## Neue Architekturprinzipien + +### 1. Observability als Qualitaets-Layer + +Beyond AI erweitert die klassische Produktarchitektur um einen expliziten +Qualitaets-Layer ueber alle FinCrime-Module hinweg: + +- `Business-Qualitaet`: erkennt das System noch das Richtige? +- `Daten-Qualitaet`: kann das Institut der Entscheidungsbasis trauen? +- `IT-Service-Qualitaet`: laeuft die Erkennungskette noch technisch integer? + +Die entscheidende Regel lautet: Nicht jedes Data- oder IT-Signal gehoert ins +Compliance-Cockpit. Sichtbar werden nur Signale mit nachweisbarer +`Business Impact`-Wirkung auf Regeln, Modelle, Populationen, Faelle oder +Kontrollhandlungen. + +### 2. Foederatives Schichtenmodell + +Die Beyond-AI-Architektur folgt einem dreistufigen Sichtbarkeitsmodell: + +- `Public Layer`: alles, was aus Regulatorik, oeffentlichen Typologien und + offenen Datenquellen ohnehin sichtbar ist +- `Federated Layer`: institutionsuebergreifende Signale, Regeln und Muster ohne + pauschalen Rohdatenaustausch +- `Internal Layer`: bankspezifische Modelle, Graphen, Entscheidungs- und + Priorisierungslogik + +Technologie ist kopierbar. Netzwerke, Governance und geteilte +Qualitaetssicherung sind es nicht. + +### 3. Legacy-Modernisierung nach dem Strangler-Prinzip + +Beyond AI geht davon aus, dass reale AML-Landschaften nicht cloud-nativ +beginnen. Deshalb ist `Mirror First` Teil der Architektur: + +- Event Mirrors und Shadow Pipelines vor produktiver Kernablaesung +- Surrogat-IDs mit `Confidence Levels`, wenn keine echte End-to-End-Trace-ID + existiert +- Strangeln von Faehigkeiten statt romantischer Komplett-Ablage von Systemen +- Vendor-Lock-in bedeutet oft: zuerst Umgebung stranglen, nicht den Kern + +## Architekturtexte + +Die Repo-Architektur wird nicht nur im Code, sondern auch in den Watchdog- +Texten entfaltet: + +- [Beyond AI/FinCrime OS β€” Weil Technologie kopierbar ist. Netzwerke nicht.](https://watchdog.endvater.de/2026/03/beyond-ai-weil-technologie-kopierbar-ist-netzwerke-nicht/) +- [Beyond AI/FinCrime OS: Die drei Schichten der Unsichtbarkeit](https://watchdog.endvater.de/2026/03/beyond-ai-fincrime-os-die-drei-schichten-der-unsichtbarkeit/) +- Observability-Serie Teil II: Business-, Daten- und IT-Service-Qualitaet als gemeinsamer Qualitaets-Layer +- Observability-Serie Teil III: Strangler-Fig-Modernisierung fuer die Legacy-Bank + ## Quickstart ```bash @@ -115,6 +179,8 @@ docker run --rm -p 8000:8000 \ - **Graph:** Neo4j Community Edition β€” Normen, EntitΓ€ten, Sanktionen, UBO-Netzwerke - **KI:** Ollama (Kimi-k2.5 / Qwen) als Primary, Claude API als Fallback β€” via `shared/llm/` - **Confidence:** FinRegAgents-Pattern β€” Retrieval Quality Gates, Composite Scoring, Phantom Detection +- **Observability:** Impact-aware Qualitaetsmodelle in `shared/observability/` fuer Detection Integrity, Data Trust und Service Health +- **Foederation:** Sichtbarkeits- und Sharing-Modelle in `shared/federation/` fuer Public/Federated/Internal Layers - **API:** FastAPI (Python) β€” REST + optional GraphQL - **Screening:** yente (OpenSanctions self-hosted API) als ElasticSearch-basiertes Matching - **Frontend:** React (minimal MVP) oder Streamlit (Prototyp) @@ -136,6 +202,11 @@ DSGV, BVR oder ein vergleichbarer Verband institutionalisiert das System. Die Ba Wir suchen Institute β€” Sparkassen, Volksbanken, Landesbanken β€” die den genossenschaftlichen Gedanken in Code ΓΌbersetzen wollen. +Das technische Modell dazu ist jetzt explizit in [`federation/`](federation) +dokumentiert: nicht nur als Betriebsform, sondern als mehrschichtige +Detection-Architektur aus sichtbaren, foederierten und vollstaendig internen +Erkennungsebenen. + ## Artikelserie Dieses Repo begleitet die Artikelserie **β€žBeyond AI β€” Das fΓΆderative Manifest in sieben Akten"** auf [FinCrime Watchdog](https://watchdog.endvater.de): diff --git a/federation/README.md b/federation/README.md new file mode 100644 index 0000000..2f00a41 --- /dev/null +++ b/federation/README.md @@ -0,0 +1,69 @@ +# Beyond AI β€” Federation Layer + +**Status: 🟑 Blueprint / Architekturmodul** + +`federation/` ist die explizite Repo-Verankerung der These: + +**Technologie ist kopierbar. Netzwerke nicht.** + +Beyond AI versteht Foederation deshalb nicht nur als Betriebsform, sondern als +Detection-Architektur mit unterschiedlichen Sichtbarkeits- und +Kooperationsschichten. + +## Das Drei-Schichten-Modell + +| Layer | Sichtbarkeit fuer Externe | Typische Inhalte | Sharing-Modus | +|------|----------------------------|------------------|---------------| +| `public` | hoch | oeffentliche Typologien, Sanktionslisten, regulatorische Schwellen, Standard-KYC | public source | +| `federated` | niedrig | anonymisierte Muster, gemeinsame Heuristiken, privacy-preserving Signale, Cross-Bank-Learning | anonymized / privacy-preserving | +| `internal` | null | bankspezifische Modelle, Priorisierung, interne Graph-Analytik, proprietaere Entscheidungslogik | institution only | + +## Warum das relevant ist + +Ein professioneller Geldwaescher kennt heute viele Elemente des oeffentlichen +Layers: Typologien, Schwellenwerte, Standardkontrollen, offene Watchlists. +Er kann gegen diese sichtbare Logik optimieren. + +Ein Beyond-AI-System soll deshalb nicht nur "besser erkennen", sondern auch +die Sichtbarkeit seiner eigentlichen Erkennungstiefe steuern: + +- `public`: regulatorisch nachvollziehbar und erwartbar +- `federated`: zwischen Instituten lernfaehig, aber fuer Angreifer opak +- `internal`: vollstaendig bankspezifisch und nicht testbar von aussen + +## Foederation heisst nicht Rohdatenaustausch + +Das Repo geht bewusst nicht von pauschalem Teilen sensibler Rohdaten aus. +Foederation meint hier in erster Linie: + +- geteilte Regeln und Kuratierung +- anonymisierte oder aggregierte Muster +- privacy-preserving Signale +- gemeinsame Qualitaetssicherung +- gemeinsames Betriebs- und Governance-Modell + +## Beziehung zur Legacy-Modernisierung + +In realen Banken laesst sich nicht jede Vendor-Blackbox direkt stranglen. +Foederation hilft auch dort: + +- gemeinsame Kuratierung rund um Blackboxes +- gemeinsame Referenzdaten- und Trust-Layer +- geteilte Priorisierungs- und Kontextlogik +- Verlagerung von Wissen aus Vendor-Silos in einen gemeinsamen Layer + +## Kernartefakte + +- `VisibilityLayer` +- `SharingMode` +- `DetectionLayer` +- `FederatedCapability` + +Diese Modelle liegen in `shared/federation/`. + +## Roadmap + +- [ ] Default Detection Layers fuer Beyond AI festziehen +- [ ] Sharing-Regeln fuer Signale aus `sanctions/` definieren +- [ ] Public/Federated/Internal-Klassifikation fuer kΓΌnftige Module einfuehren +- [ ] Governance-Playbook fuer foederativen Betrieb dokumentieren diff --git a/federation/__init__.py b/federation/__init__.py new file mode 100644 index 0000000..f6e1ba3 --- /dev/null +++ b/federation/__init__.py @@ -0,0 +1 @@ +"""Beyond AI federation module placeholder.""" diff --git a/observability/README.md b/observability/README.md new file mode 100644 index 0000000..8754925 --- /dev/null +++ b/observability/README.md @@ -0,0 +1,78 @@ +# Beyond AI β€” Observability Layer + +**Status: 🟑 Blueprint / Architekturmodul** + +Der `observability/`-Layer erweitert Beyond AI um eine explizite +Qualitaetsschicht fuer FinCrime-Systeme. Nicht mehr nur: "Funktioniert das +Modul?" Sondern: "Erkennt das Institut noch verlaesslich, worauf diese Sicht +beruht - und wo sie gerade bruechig wird?" + +## Die drei Qualitaetsdimensionen + +Beyond AI behandelt Observability nicht als reines IT-Monitoring, sondern als +Layer ueber drei Ebenen: + +- `Business-Qualitaet` + Erkennen Regeln, Modelle und Workflows noch die richtigen Risiken? +- `Daten-Qualitaet` + Kann das Institut der Entscheidungsbasis trauen? +- `IT-Service-Qualitaet` + Laeuft die Erkennungskette technisch noch integer? + +## Designregeln + +1. Nicht jedes Datenproblem ist ein Compliance-Problem. +2. Nicht jede technische Degradation gehoert ins Compliance-Cockpit. +3. Sichtbar werden nur Signale mit `Business Impact`. +4. Korrelationen ohne native Trace-ID brauchen `Surrogat-IDs` mit + `Confidence Levels`. +5. Die Navigationslogik ist wichtiger als die Kachelzahl: + `fachliches Symptom -> Datenursache -> IT-Ursache -> Massnahme`. + +## Architektur + +``` + Beyond AI Product Modules + sanctions/ horizon/ osint/ + β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + observability/ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ β”‚ β”‚ +Detection Integrity Data Trust Service Health + β”‚ β”‚ β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + Business Impact Map + β”‚ + Compliance Cockpit +``` + +## Kernartefakte + +- `QualitySignal` +- `CorrelationHandle` +- `IncidentSeverity` +- `SurfaceTarget` + +Diese Modelle liegen in `shared/observability/` und sind bewusst +moduluebergreifend formuliert: Der Sanctions Screener, der Horizon Scanner und +spaetere Graph- oder Workflow-Komponenten sollen dieselbe Sprache fuer +Qualitaet und Impact sprechen. + +## Was dieser Layer bewusst nicht ist + +- kein allgemeines NOC-Dashboard +- kein ungefilterter Data-Quality-Alarmstrom +- kein Ersatz fuer Modellvalidierung oder interne Revision +- kein Versuch, jede lokale technische Stoerung in die Compliance zu werfen + +## Roadmap + +- [ ] Impact-aware Signals aus `sanctions/` einspeisen +- [ ] Read-only Detection Integrity fuer Vendor-TM-Exports vorbereiten +- [ ] Data-Trust-Indikatoren fuer Referenzdatenfeeds definieren +- [ ] Service-Health-Signale mit Business Impact verknuepfen +- [ ] Compliance-Cockpit-Projektion als eigene API/Oberflaeche ableiten diff --git a/observability/__init__.py b/observability/__init__.py new file mode 100644 index 0000000..71b408d --- /dev/null +++ b/observability/__init__.py @@ -0,0 +1 @@ +"""Beyond AI observability module placeholder.""" diff --git a/pyproject.toml b/pyproject.toml index 67ebde1..b3d7241 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,4 +11,4 @@ ignore = [ [tool.pytest.ini_options] asyncio_mode = "auto" -testpaths = ["sanctions/tests"] +testpaths = ["sanctions/tests", "tests"] diff --git a/shared/confidence/__init__.py b/shared/confidence/__init__.py index e69de29..9761904 100644 --- a/shared/confidence/__init__.py +++ b/shared/confidence/__init__.py @@ -0,0 +1,35 @@ +"""Shared confidence primitives for Beyond AI.""" + +from enum import StrEnum + + +class ConfidenceLevel(StrEnum): + """Small shared confidence scale for model, retrieval and correlation signals.""" + + LOW = "low" + MEDIUM = "medium" + HIGH = "high" + + @property + def rank(self) -> int: + return { + ConfidenceLevel.LOW: 0, + ConfidenceLevel.MEDIUM: 1, + ConfidenceLevel.HIGH: 2, + }[self] + + def at_least(self, minimum: "ConfidenceLevel") -> bool: + return self.rank >= minimum.rank + + @classmethod + def from_score(cls, score: float) -> "ConfidenceLevel": + if not 0.0 <= score <= 1.0: + raise ValueError("score must be between 0.0 and 1.0") + if score >= 0.85: + return cls.HIGH + if score >= 0.60: + return cls.MEDIUM + return cls.LOW + + +__all__ = ["ConfidenceLevel"] diff --git a/shared/federation/__init__.py b/shared/federation/__init__.py new file mode 100644 index 0000000..6efed61 --- /dev/null +++ b/shared/federation/__init__.py @@ -0,0 +1,17 @@ +"""Shared federation models for Beyond AI.""" + +from .models import ( + DetectionLayer, + FederatedCapability, + SharingMode, + VisibilityLayer, + default_detection_layers, +) + +__all__ = [ + "DetectionLayer", + "FederatedCapability", + "SharingMode", + "VisibilityLayer", + "default_detection_layers", +] diff --git a/shared/federation/models.py b/shared/federation/models.py new file mode 100644 index 0000000..ab77bd2 --- /dev/null +++ b/shared/federation/models.py @@ -0,0 +1,95 @@ +"""Federated visibility and sharing models for Beyond AI.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import StrEnum + + +class VisibilityLayer(StrEnum): + """Visibility classes for the Beyond-AI detection architecture.""" + + PUBLIC = "public" + FEDERATED = "federated" + INTERNAL = "internal" + + +class SharingMode(StrEnum): + """How knowledge is allowed to travel across institutional boundaries.""" + + PUBLIC_SOURCE = "public_source" + ANONYMIZED_SIGNAL = "anonymized_signal" + PRIVACY_PRESERVING = "privacy_preserving" + INSTITUTION_ONLY = "institution_only" + + +@dataclass(frozen=True) +class DetectionLayer: + """One architectural visibility layer in the federated model.""" + + layer: VisibilityLayer + title: str + description: str + externally_visible: bool + sharing_mode: SharingMode + raw_data_shared: bool = False + + def hidden_from_adversary(self) -> bool: + """True when external actors should not see the logic of this layer.""" + return not self.externally_visible + + +@dataclass(frozen=True) +class FederatedCapability: + """Capability that can operate in one of the three federation layers.""" + + name: str + layer: VisibilityLayer + sharing_mode: SharingMode + description: str + raw_data_shared: bool = False + + def allows_cross_institution_learning(self) -> bool: + return self.layer == VisibilityLayer.FEDERATED + + def requires_internal_only_operation(self) -> bool: + return self.layer == VisibilityLayer.INTERNAL + + +def default_detection_layers() -> tuple[DetectionLayer, ...]: + """Reference implementation of the three visibility layers from the manifesto.""" + return ( + DetectionLayer( + layer=VisibilityLayer.PUBLIC, + title="Public Layer", + description=( + "Visible controls built from public typologies, open watchlists " + "and regulatorily expected baseline checks." + ), + externally_visible=True, + sharing_mode=SharingMode.PUBLIC_SOURCE, + raw_data_shared=False, + ), + DetectionLayer( + layer=VisibilityLayer.FEDERATED, + title="Federated Layer", + description=( + "Cross-institution signals, shared curation and privacy-preserving " + "patterns that remain opaque for external actors." + ), + externally_visible=False, + sharing_mode=SharingMode.PRIVACY_PRESERVING, + raw_data_shared=False, + ), + DetectionLayer( + layer=VisibilityLayer.INTERNAL, + title="Internal Layer", + description=( + "Institution-specific graph logic, prioritization, hidden thresholds " + "and internal models that are not externally testable." + ), + externally_visible=False, + sharing_mode=SharingMode.INSTITUTION_ONLY, + raw_data_shared=False, + ), + ) diff --git a/shared/observability/__init__.py b/shared/observability/__init__.py new file mode 100644 index 0000000..724f89f --- /dev/null +++ b/shared/observability/__init__.py @@ -0,0 +1,19 @@ +"""Shared observability models for Beyond AI.""" + +from .models import ( + CorrelationHandle, + CorrelationMethod, + IncidentSeverity, + QualityDomain, + QualitySignal, + SurfaceTarget, +) + +__all__ = [ + "CorrelationHandle", + "CorrelationMethod", + "IncidentSeverity", + "QualityDomain", + "QualitySignal", + "SurfaceTarget", +] diff --git a/shared/observability/models.py b/shared/observability/models.py new file mode 100644 index 0000000..e721405 --- /dev/null +++ b/shared/observability/models.py @@ -0,0 +1,107 @@ +"""Impact-aware observability primitives for Beyond AI.""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import StrEnum + +from shared.confidence import ConfidenceLevel + + +class QualityDomain(StrEnum): + """Which quality plane emitted a signal.""" + + BUSINESS = "business" + DATA = "data" + SERVICE = "service" + + +class IncidentSeverity(StrEnum): + """Common severity scale for quality-layer incidents.""" + + INFO = "info" + DEGRADED = "degraded" + MATERIAL = "material" + CRITICAL = "critical" + + +class SurfaceTarget(StrEnum): + """Where a signal should appear operationally.""" + + PLATFORM_TEAM = "platform_team" + DATA_TEAM = "data_team" + COMPLIANCE_COCKPIT = "compliance_cockpit" + EXECUTIVE_ESCALATION = "executive_escalation" + + +class CorrelationMethod(StrEnum): + """How a signal was correlated across system boundaries.""" + + NATIVE = "native" + SURROGATE = "surrogate" + + +@dataclass(frozen=True) +class CorrelationHandle: + """Reference used to tie a quality signal to a process or entity.""" + + name: str + value: str + method: CorrelationMethod = CorrelationMethod.NATIVE + confidence: ConfidenceLevel = ConfidenceLevel.HIGH + + def requires_review(self) -> bool: + """Surrogate correlations below high confidence need operator review.""" + return self.method == CorrelationMethod.SURROGATE and self.confidence != ConfidenceLevel.HIGH + + +@dataclass(frozen=True) +class QualitySignal: + """Cross-layer signal for Detection Integrity, Data Trust and Service Health.""" + + name: str + domain: QualityDomain + severity: IncidentSeverity + summary: str + correlation_handles: tuple[CorrelationHandle, ...] = () + affected_capabilities: tuple[str, ...] = () + business_impact: bool = False + manual_control_required: bool = False + affected_population: int | None = None + + def should_surface_to_compliance(self) -> bool: + """ + Only surface issues to the compliance cockpit when they change the + institution's effective field of view. + """ + return ( + self.business_impact + or self.manual_control_required + or bool(self.affected_capabilities) + ) + + def surface_targets(self) -> tuple[SurfaceTarget, ...]: + """Determine the minimum set of target audiences for a signal.""" + targets: list[SurfaceTarget] = [] + if self.domain == QualityDomain.DATA: + targets.append(SurfaceTarget.DATA_TEAM) + else: + targets.append(SurfaceTarget.PLATFORM_TEAM) + + if self.should_surface_to_compliance(): + targets.append(SurfaceTarget.COMPLIANCE_COCKPIT) + + if self.severity == IncidentSeverity.CRITICAL and self.should_surface_to_compliance(): + targets.append(SurfaceTarget.EXECUTIVE_ESCALATION) + + return tuple(targets) + + def lowest_correlation_confidence(self) -> ConfidenceLevel | None: + """Return the weakest confidence rating across all attached handles.""" + if not self.correlation_handles: + return None + + return min( + (handle.confidence for handle in self.correlation_handles), + key=lambda level: level.rank, + ) diff --git a/tests/test_architecture_models.py b/tests/test_architecture_models.py new file mode 100644 index 0000000..e8ddd7c --- /dev/null +++ b/tests/test_architecture_models.py @@ -0,0 +1,104 @@ +"""Tests for shared observability and federation architecture models.""" + +from shared.confidence import ConfidenceLevel +from shared.federation import SharingMode, VisibilityLayer, default_detection_layers +from shared.observability import ( + CorrelationHandle, + CorrelationMethod, + IncidentSeverity, + QualityDomain, + QualitySignal, + SurfaceTarget, +) + + +def test_confidence_level_from_score(): + assert ConfidenceLevel.from_score(0.2) == ConfidenceLevel.LOW + assert ConfidenceLevel.from_score(0.75) == ConfidenceLevel.MEDIUM + assert ConfidenceLevel.from_score(0.95) == ConfidenceLevel.HIGH + + +def test_surrogate_correlation_requires_review_below_high_confidence(): + surrogate = CorrelationHandle( + name="batch_cycle_id", + value="2026-04-04T09:00", + method=CorrelationMethod.SURROGATE, + confidence=ConfidenceLevel.MEDIUM, + ) + native = CorrelationHandle( + name="trace_id", + value="abc-123", + method=CorrelationMethod.NATIVE, + confidence=ConfidenceLevel.HIGH, + ) + + assert surrogate.requires_review() is True + assert native.requires_review() is False + + +def test_quality_signal_only_surfaces_to_compliance_with_business_impact(): + signal = QualitySignal( + name="schema drift", + domain=QualityDomain.DATA, + severity=IncidentSeverity.DEGRADED, + summary="Local schema drift without proven downstream impact.", + ) + + assert signal.should_surface_to_compliance() is False + assert signal.surface_targets() == (SurfaceTarget.DATA_TEAM,) + + +def test_quality_signal_with_business_impact_reaches_compliance_and_exec(): + signal = QualitySignal( + name="sanctions source delayed", + domain=QualityDomain.DATA, + severity=IncidentSeverity.CRITICAL, + summary="Reference data delay affects sanctions hit quality.", + affected_capabilities=("sanctions screening",), + business_impact=True, + ) + + assert signal.should_surface_to_compliance() is True + assert signal.surface_targets() == ( + SurfaceTarget.DATA_TEAM, + SurfaceTarget.COMPLIANCE_COCKPIT, + SurfaceTarget.EXECUTIVE_ESCALATION, + ) + + +def test_quality_signal_reports_lowest_correlation_confidence(): + signal = QualitySignal( + name="legacy batch mismatch", + domain=QualityDomain.SERVICE, + severity=IncidentSeverity.MATERIAL, + summary="Batch and case flow disagree.", + correlation_handles=( + CorrelationHandle( + name="job_run_id", + value="run-1", + method=CorrelationMethod.NATIVE, + confidence=ConfidenceLevel.HIGH, + ), + CorrelationHandle( + name="file_delivery_id", + value="delivery-42", + method=CorrelationMethod.SURROGATE, + confidence=ConfidenceLevel.LOW, + ), + ), + ) + + assert signal.lowest_correlation_confidence() == ConfidenceLevel.LOW + + +def test_default_detection_layers_model_public_federated_internal(): + layers = default_detection_layers() + + assert [layer.layer for layer in layers] == [ + VisibilityLayer.PUBLIC, + VisibilityLayer.FEDERATED, + VisibilityLayer.INTERNAL, + ] + assert layers[0].externally_visible is True + assert layers[1].sharing_mode == SharingMode.PRIVACY_PRESERVING + assert layers[2].hidden_from_adversary() is True