← Zurück zum Journal
Governance

Behaviour-first Governance in der Praxis — Artefakte aus Plattform-Ereignissen erzeugen

László Hadházy·3. August 2026·18 Min.

Data GovernanceGovernanceDORADSGVODACHAWSOpenlineagePolicy As Code

Begleitbeitrag zum Data-Governance-Grundpfeiler. Der Grundpfeiler führt das Argument für Behaviour-first Governance auf der Ebene warum die Artefakte erzeugt und nicht getippt werden sollten. Dieser Beitrag setzt genau dort an, wo der Grundpfeiler aufhört: bei der Frage, wie man sie tatsächlich erzeugt — auf dem AWS-nativen Stack aus Fabric + Mesh auf AWS.

Was der Grundpfeiler offen ließ

Der zentrale Anspruch des Grundpfeilers, wert wiederholt zu werden:

Governance ist das, was die Plattform durchsetzt, was die Fachbereiche tun und was die Nachweise zeigen — in dieser Reihenfolge. Die Artefakte (Richtlinien, RACIs, RoPAs) sind Nebenprodukte dieses Verhaltens, keine Lieferergebnisse an sich.

Das zu glauben ist eine Sache. Die Rohre zu bauen, die es wahr machen, ist eine andere. Dieser Beitrag geht vier Mechanismen durch — die, die „wir sollten den RoPA aus Lineage erzeugen" in ein echtes Terraform-Modul und eine echte SQL-Abfrage verwandeln — auf dem AWS-nativen Stack, den die Fabric + Mesh auf AWS-Referenz bereits etabliert: LakeFormation + DataZone + Glue, plus OpenLineage als anbieter-neutrales Lineage-Rückgrat.

Die vier Mechanismen:

  1. RoPA aus Lineage — DSGVO-Art.-30-Datensätze, projiziert aus OpenLineage-Ereignissen + LF-Tag-Ontologie + DataZone-Subscription-Log.
  2. RACI aus Katalog-Eigentum — die RACI-Matrix abgeleitet aus DataZone-Projekt-Eigentum + LF-Tag-Metadaten + Abonnement-Genehmigungen.
  3. Audit-Bericht aus Subscription-Log — die Frage „wer hat was in den letzten 90 Tagen gelesen" beantwortet aus CloudTrail + DataZone-Ereignissen, in dem Format, das Aufseher akzeptieren.
  4. Änderungs-Autorisierungs-Workflow — GitOps für Policy-as-Code, bei dem der signierte Commit das audit-belegte Änderungs-Protokoll ist, mit automatisch erzeugten Betriebsrats-Meldungsentwürfen bei RBAC-relevanten Änderungen.

Keiner davon ist Cordata-spezifische Erfindung. Die Primitive existieren. Was den meisten Programmen fehlt, ist die Verdrahtung — die spezifischen Projektionen, die Feld-Zuordnungen, die Frage, wo die menschliche Prüfung sitzt. Das ist, was dieser Beitrag behandelt.

§ 1 — Woher die Ereignisse kommen

Vor den vier Mechanismen die Ereignisquellen, aus denen sie alle lesen. Alle vier Projektionen ziehen aus denselben drei, und diese Quellen zum Laufen zu bringen ist Voraussetzung dafür, dass irgendeine der Projektionen ehrlich ist:

  • OpenLineage-Ereignisse — jeder Job (dbt-Modell-Lauf, Glue-ETL-Job, Airflow-Task, EMR-Spark-Job) emittiert ein RunEvent mit Input-Datensätzen, Output-Datensätzen, Job-Metadaten und Lauf-Status. Landet in einem OpenLineage-kompatiblen Backend. Marquez ist das OSS-Referenz-Backend, und Amazon DataZone akzeptiert OpenLineage-Ereignisse direkt über seine PostLineageEvent-API (angekündigt 2024, seither gereift — prüfen Sie die aktuellen AWS-Docs für den GA-Status und die spezifische Facet-Abdeckung). Auf dem AWS-nativen Stack bedeutet das, dass die OpenLineage-Emitter — das Spark-Plugin für Glue-Jobs, openlineage-airflow für MWAA, dbt's eingebauter OpenLineage-Adapter — direkt an DataZone posten können, mit einem dünnen Adapter, der ihre Payloads in DataZones Ausprägung übersetzt. Die Projektionen in §§ 2-4 kümmern sich nicht darum, welches Backend die Ereignisse tatsächlich hält; die Ereignis-Senke ist von den Emittern abstrahiert. Wir behandeln diese Pipeline als die Quelle der Wahrheit für welche Daten durch welchen Job geflossen sind.
  • DataZone-Subscription-Log — jede Asset-Veröffentlichung, jede Abonnement-Anfrage, jede Genehmigung und jeder Widerruf ist ein DataZone-Ereignis. Verfügbar über die DataZone-API und in CloudTrail gespiegelt. Das ist die Quelle der Wahrheit für wer darf welches Datenprodukt sehen.
  • CloudTrail-Data-Events — für die Tabellen unter LakeFormation-Verwaltung erfassen CloudTrail-Data-Events das Zugriffs-Log pro Abfrage (GetTable, GetPartitions, Athena StartQueryExecution, Glue GetPartitions, etc.). Data-Events müssen explizit aktiviert werden — sie sind nicht standardmäßig an und sind das, was die Audit-Antwort von Vermutung zu Beweis macht.
# Data-plane CloudTrail on the governance account, capturing LakeFormation
# access events across all producer accounts (cross-account CloudTrail).
resource "aws_cloudtrail" "governance_data_events" {
  name                          = "cordata-gov-data-events"
  s3_bucket_name                = aws_s3_bucket.audit.id
  is_organization_trail         = true
  is_multi_region_trail         = true
  include_global_service_events = true
 
  event_selector {
    read_write_type           = "All"
    include_management_events = true
 
    data_resource {
      type = "AWS::S3::Object"
      # Registered lake buckets — the ones LakeFormation manages
      values = ["arn:aws:s3:::cordata-lake-*/*"]
    }
 
    data_resource {
      type   = "AWS::Glue::Table"
      values = ["arn:aws:glue:*:*:table/*"]
    }
  }
}

Mit diesen drei Quellen an Ort und Stelle ist alles weiter unten eine Projektion — ein Abfrage-Muster, das Rohereignisse in das Governance-Artefakt verwandelt.

§ 2 — RoPA aus Lineage

Verzeichnis von Verarbeitungstätigkeiten (RoPA, gemäß DSGVO Art. 30) ist das Artefakt, über das die meisten Governance-Programme lügen. Einmal getippt, von Hand gepflegt, binnen eines Quartals falsch. Ihn aus dem Ereignis-Strom der Plattform selbst zu erzeugen entfernt das Lügen.

Kurzer Primer für Leser, die nicht in Art. 30 verwurzelt sind. Der RoPA ist ein Register, das jeder Verantwortliche und Auftragsverarbeiter personenbezogener Daten führen muss — Art. 30(1) für Verantwortliche, Art. 30(2) für Auftragsverarbeiter. Er dokumentiert, pro Verarbeitungstätigkeit: den Zweck der Verarbeitung, die Kategorien der Daten und betroffenen Personen, die Empfängerkategorien, etwaige grenzüberschreitende Übermittlungen, Aufbewahrungsfristen und die eingesetzten technischen und organisatorischen Sicherheitsmaßnahmen. Eine Aufsichtsbehörde (BfDI in Deutschland, die kantonalen Datenschutzbeauftragten in der Schweiz, die DSB in Österreich) kann ihn kurzfristig anfordern. Ein falscher RoPA ist ein dokumentierter Verstoß gegen Art. 30; ein fehlender ist ein erschwerender Faktor bei jedem nachfolgenden Audit nach Art. 83 (Bußgelder bis zu 10 Mio. € oder 2 % des weltweiten Umsatzes, je nachdem, was höher ist). Deshalb ist „wir tippen ihn einmal und pflegen ihn von Hand" eine Form, über die Governance-Programme lügen — die Pflegekosten sind real, die Aktualitätslücke wächst still, und das Risiko ist asymmetrisch, wenn es eintritt.

Der DSGVO-Art.-30-Feldsatz, den ein Datensatz auf Verantwortlicher-Seite tragen muss:

Art. 30-Feld Quelle in der Plattform
Verarbeitungszweck Job-Metadaten (dbt-Modell-Tag, Glue-Job-Tag, DataZone-Asset-Annotation)
Datenkategorien Input-Dataset-Schema + LF-Tag sensitivity-Wert
Kategorien betroffener Personen Input-Dataset-Schema + LF-Tag subject_type (Kunde, Mitarbeiter, Auftragnehmer)
Empfängerkategorien Konsumenten des Output-Datasets (über DataZone-Subscription-Log)
Drittlandübermittlungen LF-Tag residency auf Inputs vs. Outputs
Aufbewahrungsfristen Lifecycle-Policy auf Tabellenebene (S3-Lifecycle-Regeln + Glue-Tabellen-Properties)
Sicherheitsmaßnahmen Statisch — aus dem Plattform-Baseline (KMS, LF-verwaltet, VPC-Endpoints)
Rechtsgrundlage Job-Metadaten (Tag auf der Pipeline)

Bevor die SQL läuft, müssen die Ereignisse irgendwo gemeinsam abfragbar sein. Die vier Quellen — OpenLineage-Ereignisse, Katalog-Metadaten (Glue-Tabellen + LF-Tag-Bindungen), DataZone-Abonnements und CloudTrail-Data-Events — liegen standardmäßig nicht im selben Store. Zwei Wege bringen sie dorthin:

  • Zentral materialisieren — ein nächtliches ETL schifft jede Quelle in ein Data-Warehouse im Governance-Konto: Redshift Serverless, Aurora PostgreSQL oder DuckDB-über-S3 via Athena zugegriffen. Operativ einfach (eine Stelle zum Sichern, Auditieren und Sichern), veraltet nach ETL-Kadenz.
  • Föderieren mit Trino — Trino-Konnektoren fragen die Quellen an Ort und Stelle ab, ohne Daten zu bewegen. Eine einzige Abfrage joint das OpenLineage-Backend, den Glue-Katalog, das DataZone-Subscription-Log und den CloudTrail-Export direkt. Frischer (auf die Minute aktuell), mehr bewegliche Teile zu pflegen (Konnektor-Configs, Credentials pro Quelle, Kostenzuweisung zur Abfragezeit).

Cordatas Standard-Empfehlung ist zentral materialisieren für das erste Jahr eines Governance-Programms — ein Warehouse, ein Team, eine Grant-Oberfläche, eine Stelle, an die ein Aufseher schauen kann. Trino-Föderation ist die Komplexität wert, wenn der RoPA auf die Minute aktuell sein muss (selten) oder wenn das Quell-Ereignis-Volumen ETL unpraktisch macht.

Die Ereignis-Sammlungs-Verdrahtung selbst — wie die OpenLineage-Senke landet, wie CloudTrail nach Athena exportiert, der Glue-Crawler-Zeitplan — ist außerhalb des Umfangs dieses Beitrags. Das ist konkretes Terrain für den Pipeline-Hälften-Begleitbeitrag in den Folgebeiträgen des Governance-Grundpfeilers.

Wenn die Ereignisse an einem Ort landen, ist die Projektion ein einzelnes dbt-Modell, das OpenLineage plus die LF-Tag-Ontologie plus das DataZone-Subscription-Log liest:

-- models/governance/ropa_current.sql
-- Materialised as a view, refreshed every 6 hours by an EventBridge
-- schedule. Human review + signature happens on the delta since the
-- last signed snapshot (see the HITL step at the end of this section).
 
WITH lineage_jobs AS (
  SELECT
    ol.job_namespace,
    ol.job_name,
    ol.job_facets->'processing'->>'purpose' AS purpose,
    ol.job_facets->'processing'->>'legal_basis' AS legal_basis,
    ol.inputs,
    ol.outputs,
    ol.run_id,
    ol.event_time
  FROM openlineage_events ol
  WHERE ol.event_type = 'COMPLETE'
    AND ol.event_time > NOW() - INTERVAL '30 days'
),
input_facets AS (
  SELECT
    j.run_id,
    lft.sensitivity,
    lft.residency,
    lft.subject_type,
    d.name AS dataset_name,
    d.schema_fields AS fields
  FROM lineage_jobs j
  CROSS JOIN LATERAL jsonb_array_elements(j.inputs) AS input
  JOIN datasets d ON d.namespace = input->>'namespace'
                 AND d.name      = input->>'name'
  JOIN lf_tag_bindings lft ON lft.dataset_name = d.name
),
output_facets AS (
  SELECT
    j.run_id,
    d.name AS output_name,
    lft.residency AS output_residency
  FROM lineage_jobs j
  CROSS JOIN LATERAL jsonb_array_elements(j.outputs) AS output
  JOIN datasets d ON d.namespace = output->>'namespace'
                 AND d.name      = output->>'name'
  JOIN lf_tag_bindings lft ON lft.dataset_name = d.name
),
recipients AS (
  -- Downstream consumers via DataZone subscription log
  SELECT
    of.output_name,
    array_agg(DISTINCT dz.subscriber_project) AS consuming_domains
  FROM output_facets of
  JOIN datazone_subscriptions dz ON dz.asset_name = of.output_name
                                 AND dz.status = 'APPROVED'
  GROUP BY of.output_name
)
SELECT
  j.purpose                                    AS processing_purpose,
  j.legal_basis                                AS legal_basis,
  array_agg(DISTINCT i.subject_type)           AS data_subject_categories,
  array_agg(DISTINCT i.sensitivity)            AS data_categories,
  array_agg(DISTINCT i.residency)              AS source_residencies,
  array_agg(DISTINCT o.output_residency)       AS output_residencies,
  (SELECT array_agg(consuming_domains)
     FROM recipients r
     JOIN output_facets o2 ON r.output_name = o2.output_name
     WHERE o2.run_id = j.run_id)               AS recipient_categories,
  j.job_namespace || '.' || j.job_name         AS processing_activity_id,
  MAX(j.event_time)                            AS last_seen
FROM lineage_jobs j
JOIN input_facets  i ON i.run_id = j.run_id
JOIN output_facets o ON o.run_id = j.run_id
GROUP BY j.job_namespace, j.job_name, j.purpose, j.legal_basis, j.run_id;

Beispielausgabe — drei Zeilen aus ropa_current:

processing_activity_id legal_basis data_subject_categories data_categories source_residencies output_residencies recipient_categories
fraud.transactions_scored_daily legitimate-interest {customer} {high, confidential} {eu} {eu} {analytics-domain, ml-domain}
ml.churn_predictions_weekly consent {customer} {high} {eu} {eu} {retention-domain}
finance.bafin_quarterly_extract legal-obligation {customer, contractor} {high, confidential} {eu} {eu, ch} {finance-domain, regulator-portal}

Die Erkennung grenzüberschreitender Übermittlung ist erwähnenswert. Wenn source_residencies und output_residencies sich unterscheiden — z. B. {eu} rein, {eu, us} raus —, ist diese Zeile ein Kandidat für eine grenzüberschreitende Übermittlung nach DSGVO Art. 44-49 und braucht eine dokumentierte Schutzmaßnahme (SCCs, Angemessenheitsbeschluss oder ausdrückliche Einwilligung). Die dritte Zeile oben ist genau dieses Muster ({eu}{eu, ch}). Die Projektion macht es sichtbar; das Governance-Council entscheidet, ob die Übermittlung autorisiert ist.

Wo die menschliche Verantwortung sitzt. Die Projektion erzeugt den Zustand des RoPA zu jedem Moment; DSGVO Art. 30 verlangt einen signierten Datensatz mit einer benannten Verantwortlichen-Seite-Autorität. Diese Signatur ist die des Datenschutzbeauftragten (oder des in der Governance-RACI benannten Datenschutz-Leads) — niemand sonst in der Organisation ist zur Signatur autorisiert, und diese Identität ist die Verantwortungskette, die DSGVO Art. 30 verlangt. Die HITL-Kadenz: wöchentlich bei unwesentlichen Änderungen, am nächsten Werktag bei wesentlichen (Datenkategorie hinzugefügt, Empfänger hinzugefügt, Flag für grenzüberschreitende Übermittlung umgelegt), sodass der RoPA nie mehr als sieben Tage veraltet ist.

Das vollständige, einsetzbare Framework — EventBridge-Zeitplan, ropa_snapshots-Warehouse-Tabelle, Diff-und-Post-Lambda, GPG-signiertes Audit-Repo, Frische-Trigger, echte Frische-Metriken nach dem Betrieb — ist Terrain für den Folgebeitrag: Wie wir das RoPA-Erzeugungs-Framework gebaut haben (geplant). Die Aufgabe dieses Beitrags ist das Muster; die Aufgabe jenes Beitrags ist der einsetzbare Beweis.

Die Form ist, was ein DSGVO-Auditor akzeptiert: keine von Hand gepflegte Wiki-Seite, sondern ein erzeugter Datensatz mit einem benannten Unterzeichner, einer signierten Nachweiskette und einer Prüfkadenz, die ein Aufseher einsehen kann.

§ 3 — RACI aus Katalog-Eigentum

Die RACI-Matrix — Responsible, Accountable, Consulted, Informed — ist ein Governance-Artefakt, das binnen eines Quartals nach dem Schreiben veraltet. Der Grund ist banal: die Organisation ändert sich, das Papier nicht. Der Behaviour-First-Zug: leiten Sie die RACI aus den Eigentümer-Metadaten des Katalogs selbst ab, die die Fachbereiche aktuell halten, weil ihr eigener Abonnement-Workflow davon abhängt.

Die Zuordnung auf dem AWS-nativen Stack:

RACI-Rolle Abgeleitet aus
Accountable (A) DataZone-Projekt-Eigentümer für das Datenprodukt
Responsible (R) Fachbereichsmitglieder mit SELECT + INSERT + UPDATE-Grants über LF-Tag-Policy
Consulted (C) Nachgelagerte Konsumenten via DataZone-Abonnement (Status APPROVED)
Informed (I) Governance-Council-Rolle + Audit-Log-Abonnenten

Eine einzige Abfrage macht die vollständige Matrix für jedes Datenprodukt sichtbar:

-- models/governance/raci_derived.sql
-- Executes against the catalog + subscription log. Runs on-demand
-- (governance dashboard) or nightly (reconciliation against paper-RACI).
 
SELECT
  ap.asset_name,
  ap.data_product,
 
  -- Accountable: the DataZone project owner
  ap.owning_project_owner_email AS accountable,
 
  -- Responsible: domain members with write-grants
  (SELECT array_agg(DISTINCT lfp.principal_arn)
     FROM lf_permissions lfp
     WHERE lfp.resource_expression @> ap.lf_tag_expression
       AND lfp.permissions && ARRAY['SELECT', 'INSERT', 'UPDATE']
       AND lfp.principal_arn LIKE '%' || ap.owning_domain || '%'
  ) AS responsible,
 
  -- Consulted: approved downstream subscribers
  (SELECT array_agg(DISTINCT dz.subscriber_project)
     FROM datazone_subscriptions dz
     WHERE dz.asset_name = ap.asset_name
       AND dz.status = 'APPROVED'
  ) AS consulted,
 
  -- Informed: governance council + audit subscribers (static config)
  ARRAY['governance-council@cordata', 'audit-log@cordata'] AS informed
 
FROM asset_publications ap;

Beispielausgabe — zwei Zeilen aus raci_derived:

asset_name accountable responsible consulted informed
fraud.transactions_curated fraud-lead@cordata {fraud-eng-1@, fraud-eng-2@} {analytics-domain, ml-domain} {governance-council, audit-log}
policy.customer policy-lead@cordata {policy-eng-1@} {claims-domain, marketing-domain} {governance-council, audit-log}

Die interessante Ausgabe ist nicht die Projektion selbst — es ist die Rückkopplung. Wöchentlich vergleicht ein Job die abgeleitete RACI mit der Papier-RACI (typischerweise eine Tabelle, die Legal pflegt) und macht Abweichungen sichtbar:

-- models/governance/raci_divergence.sql
SELECT
  derived.asset_name,
  derived.accountable AS derived_accountable,
  paper.accountable   AS paper_accountable,
  CASE
    WHEN derived.accountable != paper.accountable THEN 'accountable_mismatch'
    WHEN derived.responsible != paper.responsible THEN 'responsible_drift'
    WHEN cardinality(derived.consulted) != cardinality(paper.consulted) THEN 'subscriber_drift'
    ELSE 'aligned'
  END AS divergence_type
FROM raci_derived derived
LEFT JOIN raci_paper paper ON paper.asset_name = derived.asset_name
WHERE derived.accountable != paper.accountable
   OR derived.responsible != paper.responsible
   OR cardinality(derived.consulted) != cardinality(paper.consulted);

Beispielausgabe — eine Governance-Council-Prüfung würde auf Zeilen wie diese treffen:

asset_name derived_accountable paper_accountable divergence_type
policy.customer policy-lead@cordata old-owner@cordata accountable_mismatch
claims.claim_events claims-lead@cordata claims-lead@cordata subscriber_drift

Abweichung als Signal. Wenn die abgeleitete RACI von der Papier-RACI abweicht, ist das an sich ein Datenqualitäts-Ereignis. Zwei Möglichkeiten:

  • Das Papier ist veraltet — die abgeleitete Version ist Wahrheit, und das Papier muss aktualisiert werden (oder, besser, zugunsten der abgeleiteten Ansicht in Rente geschickt).
  • Die Katalog-Metadaten sind falsch — ein Fachbereich hat sein Eigentum nicht aktualisiert; die Plattform setzt veraltete Grants gegen veraltete Metadaten durch. So oder so hat die Abweichung einen spezifischen Eigentümer und einen spezifischen Fix.

Das Governance-Council prüft Abweichungen monatlich. Die Papier-RACI hört mit der Zeit auf, die Quelle der Wahrheit zu sein — Teams hören auf, sie zu pflegen, sobald sie der abgeleiteten Ansicht vertrauen. Das ist nicht die Aufgabe der Governance; es ist ihre Reife.

§ 4 — Audit-Bericht aus Subscription-Log

Die Audit-Frage, die ein DACH-Aufseher stellt — BaFin, FINMA oder eine interne Prüfung —, hat fast immer dieselbe Form:

„Zeigen Sie mir, wer in den letzten 90 Tagen auf die Kunden-Tabelle zugegriffen hat, mit Query-Text, anfragender Rolle und der Richtlinie, die den Zugriff gewährt hat."

Diese Antwort aus CloudTrail + DataZone-Ereignissen zu bekommen erfordert echtes Engineering, aber keine exotischen Kunststücke. Der Haken ist, dass die Ereignisse über Konten verteilt sind (Producer-Konten pro Fachbereich für die Storage-Layer-Ereignisse, das Governance-Konto für die Grant-Layer-Ereignisse), sodass die kontoübergreifende Ereignis-Verknüpfung die eigentliche Arbeit ist.

Das Ereignis-Verknüpfungs-Muster:

Governance account

Producer account (customer domain)

Consumer account

session ID

assumed-role ARN

authorisation basis

Athena StartQueryExecution

AssumeRole into producer

LakeFormation GetTemporaryGlueTableCredentials

S3 GetObject on customer/*

LF-tag policy evaluated

Subscription approval event

Diagramm-Erläuterung. Die Athena-Abfrage eines Konsumenten in einem Konto startet eine Session (A1). Die Session assumiert eine kontoübergreifende Rolle im Producer-Konto (A2). LakeFormation vergibt temporäre Credentials für die Glue-Tabelle (P1), die die LF-Tag-Policy im Governance-Konto (G1) auswertet — diese Policy ist ein materialisierter Grant aus einem DataZone-Abonnement (G2). Der S3-Read (P2) findet unter den temporären Credentials statt. Um „wer hat auf die Kunden-Tabelle zugegriffen" zu beantworten, muss die Audit-Abfrage diese Kette rückwärts vom S3-Zugriff zur menschlichen Identität laufen, die die Session gestartet hat.

Die Abfrage, die die Frage des Aufsehers beantwortet, läuft gegen einen CloudTrail-Data-Lake (z. B. Athena über den CloudTrail-S3-Export):

-- Last-90-day audit query for the customer table
WITH s3_reads AS (
  SELECT
    event_time,
    user_identity.session_context.session_issuer.arn AS assumed_role,
    request_parameters.bucket_name                   AS bucket,
    request_parameters.key                           AS object_key,
    recipient_account_id                             AS producer_account
  FROM cloudtrail_data_events
  WHERE event_source     = 's3.amazonaws.com'
    AND event_name       = 'GetObject'
    AND request_parameters.bucket_name = 'cordata-lake-customer'
    AND event_time > NOW() - INTERVAL '90' DAY
),
role_sessions AS (
  SELECT
    event_time                                       AS assume_time,
    user_identity.arn                                AS human_identity,
    request_parameters.role_arn                      AS assumed_role,
    response_elements.assumed_role_user.arn          AS session_arn
  FROM cloudtrail_events
  WHERE event_source = 'sts.amazonaws.com'
    AND event_name   = 'AssumeRole'
    AND event_time > NOW() - INTERVAL '90' DAY
),
grant_context AS (
  SELECT
    dz.approval_time,
    dz.subscriber_project,
    dz.asset_name,
    dz.approved_by,
    dz.lf_tag_expression                             AS granting_policy
  FROM datazone_subscription_events dz
  WHERE dz.status = 'APPROVED'
    AND dz.asset_name LIKE '%customer%'
)
SELECT
  r.event_time,
  s.human_identity,
  r.assumed_role,
  r.object_key,
  g.granting_policy,
  g.approved_by                                       AS grant_authorised_by
FROM s3_reads r
JOIN role_sessions s ON s.session_arn = r.assumed_role
                     AND r.event_time > s.assume_time
LEFT JOIN grant_context g ON r.object_key LIKE '%' || g.asset_name || '%'
ORDER BY r.event_time DESC;

Beispielausgabe — zwei Zeilen aus den letzten 90 Tagen, ARNs zur Lesbarkeit abgekürzt:

event_time human_identity assumed_role object_key granting_policy grant_authorised_by
2026-08-01 14:22 iam::123…:user/anna.mueller iam::987…:role/analytics-reader customer/2026/07/day=28/part-000.parquet domain=customer AND sensitivity<=confidential AND residency=eu policy-lead@cordata
2026-08-01 09:15 iam::123…:user/marc.dubois iam::987…:role/ml-training-role customer/2026/07/day=27/part-001.parquet domain=customer AND sensitivity<=confidential AND residency=eu policy-lead@cordata

Jeder Read eines cordata-lake-customer-Objekts ist jetzt zurückverbunden zu (a) der menschlichen Identität hinter der Session, (b) der assumierten Rolle, (c) der LF-Tag-Policy, die ihn erlaubt hat, und (d) der DataZone-Abonnement-Genehmigung, die diese Policy materialisierte — mit dem Namen des Fachbereichs-Eigentümers, der genehmigte.

Das ist das Format, das ein BaFin-Auditor akzeptiert. Keine aus dem Gedächtnis rekonstruierte Tabelle.

§ 5 — Änderungs-Autorisierungs-Workflow

Der zweite Zug aus § 5 des Grundpfeilers — „ein Änderungs-Autorisierungs-Workflow, der selbst audit-belegt ist" — erweist sich als der am wenigsten mysteriöse der vier Mechanismen, sobald man das Betriebs-Muster akzeptiert: GitOps für Policy-as-Code, mit signierten Commits als Änderungs-Protokoll.

Das ist keine Governance-spezifische Erfindung. Es ist dieselbe Policy-as-Code-Access-Fähigkeit, die als einer der fünf Fabric-Züge im Data-Engineering-Grundpfeiler benannt wird, auf demselben IaC-First-Fundament, das die Fabric + Mesh auf AWS-Referenz in ihrem § 2 als Voraussetzung behandelt. Governance läuft auf denselben Schienen wie alles andere, das die Plattform ausliefert — das ist der Punkt von Behaviour-First als Betriebs-Haltung, nicht nur als Phrase.

Die Form:

  • Die LF-Tag-Ontologie, die Grant-Ausdrücke, die DataZone-Projekt-Konfiguration und die Konfiguration des Abonnement-Genehmigungs-Workflows liegen alle in einem Terraform-Repo unter terraform/governance/.
  • Jede vorgeschlagene Änderung ist ein Pull Request. Der PR trägt die Ausgabe von terraform plan als angehängtes Text-Artefakt — der Reviewer sieht genau, was sich ändern wird, bevor er genehmigt.
  • Merged-PRs sind GPG-signierte Commits (git commit -S), bei denen der Schlüssel des Reviewers seiner Identität im internen Identity Provider zugeordnet ist.
  • Ein GitHub-Actions-Workflow wendet den Plan beim Merge an, und die CloudTrail-Ereignisse PutResourcePolicy / CreateLFTag, die daraus entstehen, tragen den Git-SHA als Request-Metadaten-Tag.
# .github/workflows/apply-governance.yml
name: Apply governance changes
 
on:
  push:
    branches: [main]
    paths:
      - "terraform/governance/**"
 
jobs:
  apply:
    runs-on: ubuntu-latest
    environment:
      name: production
      # Environment requires 2 approvers from the governance CODEOWNERS
      # (defined in .github/CODEOWNERS below). No lone-wolf changes.
    permissions:
      id-token: write
      contents: read
    steps:
      - uses: actions/checkout@v4
      - name: Verify signed commit
        run: |
          if [ "$(git log -1 --pretty=format:%G?)" != "G" ]; then
            echo "Commit is not GPG-verified — refusing to apply."
            exit 1
          fi
      - name: Apply with SHA tag
        env:
          COMMIT_SHA: ${{ github.sha }}
        run: |
          cd terraform/governance
          terraform init
          terraform apply -auto-approve \
            -var="change_reference=${COMMIT_SHA}"
# .github/CODEOWNERS
terraform/governance/lf-tags/**       @governance-council
terraform/governance/grants/**        @platform-team @governance-council
terraform/governance/datazone/**      @platform-team

CODEOWNERS setzt den föderierten Entscheidungs-Vertrag aus § 5 des Grundpfeilers durch: jede Änderung an der LF-Tag-Ontologie (dem gemeinsamen Vokabular) erfordert Genehmigung durch das Governance-Council; Grants erfordern Plattformteam UND Governance-Council; die DataZone-Projekt-Konfiguration gehört dem Plattformteam.

Das Änderungs-Protokoll IST der Audit-Nachweis. Wenn ein Aufseher fragt „wann wurde der Tag sensitivity=regulator_only hinzugefügt und wer hat ihn genehmigt?", ist die Antwort eine Git-Log-Abfrage, keine Wiki-Archäologie-Grabung:

git log --show-signature --patch \
  --follow terraform/governance/lf-tags/sensitivity.tf

Die Ausgabe enthält die Commit-Nachricht, den Diff, die GPG-Signatur, die Signierer-Identität und die Zeitstempel. Alles unveränderlich. Alles Beweis.

Betriebsrats-Meldungs-Integration

Der Betriebsrats-Rhythmus aus § 8 des Grundpfeilers wird zu einem konkreten Teil dieses Workflows. Jeder PR, der terraform/governance/lf-tags/** (RBAC-relevante Änderungen) berührt, erzeugt automatisch einen Betriebsrats-Meldungsentwurf:

# .github/workflows/works-council-notification.yml
on:
  pull_request:
    paths:
      - "terraform/governance/lf-tags/**"
 
jobs:
  draft-notification:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Generate works-council notification draft
        env:
          PR_NUMBER: ${{ github.event.pull_request.number }}
          PR_TITLE:  ${{ github.event.pull_request.title }}
        run: |
          python .github/scripts/draft_notification.py \
            --pr "$PR_NUMBER" \
            --title "$PR_TITLE" \
            --diff "$(git diff origin/main -- terraform/governance/lf-tags/)" \
            --template docs/works-council/notification-template.de.md \
            --out /tmp/notification.md
      - name: Attach to PR
        uses: mshick/add-pr-comment@v2
        with:
          message-path: /tmp/notification.md

Der erzeugte Entwurf ist eine ausgefüllte Kopie der Betriebsrats-Meldungs-Vorlage (die Vorlage-Datei ist auf Deutsch — .de.md — weil der Betriebsrat ein deutschsprachiges Dokument erhält) — automatisch befüllt mit dem Änderungsumfang (welche Tag-Werte hinzugefügt/entfernt/neu definiert werden), der betroffenen Datenprodukt-Oberfläche, der geschätzten Anzahl betroffener Grants und dem Rollback-Plan des Plattformteams. Der Betriebsrat erhält einen echten Entwurf, den er prüfen kann; das Plattformteam bezahlt die Prüfzyklus-Zeit vorab, statt drei Sprints später ein Rollback zu essen.

§ 6 — Was das nicht abdeckt

Die vier Mechanismen oben sind die Artefakt-Erzeugungs-Seite der Behaviour-First-Governance. Drei spezifische Dinge tun sie bewusst nicht:

  • Sie entscheiden keine Richtlinien. Die Projektionen erzeugen das Protokoll darüber, welche Richtlinien existieren und was sie durchsetzen. Das Governance-Council muss die Entscheidungen weiterhin selbst treffen — föderierte computationale Governance bedeutet computationale Durchsetzung, keine computationale Entscheidung.
  • Sie versöhnen nicht über Rechtsordnungen hinweg. Ein Multi-Jurisdiktions-Haus (DE + CH + AT mit separaten Rechtsgrundlagen) braucht einen pro-Jurisdiktion-RoPA und einen pro-Jurisdiktion-Audit-Trail. Die Projektion oben ist einheits-jurisdiktional; sie zu erweitern ist ein spezifischer Mandats-Umfang, keine generische Vorlage.
  • Sie migrieren keine Alt-Artefakte. Wenn ein Haus bereits einen manuell gepflegten RoPA und eine Papier-RACI hat, ist die Migration eine separate Anstrengung — Papier mit abgeleiteter Ansicht abgleichen, entscheiden, welche historischen Einträge übernommen werden, und mit Legal über den Übergang verhandeln. Die abgeleitete Ansicht ist stabil; die Migration ist der Ort, an dem die politische Arbeit lebt.

Wer erzeugte Governance als vollständige Antwort auf ein Governance-Programm verkauft, verpasst alle drei.

Wo das hineinpasst

Der Data-Governance-Grundpfeiler etablierte die Theorie — Governance als Verhalten mit Nachweis. Die Fabric + Mesh auf AWS-Referenz etablierte das technische Fundament. Dieser Beitrag ist die Verdrahtung zwischen ihnen.

Für ein Haus, das das ernsthaft baut, ist das zweiwöchige Discovery aus dem Engagement-Modell des Grundpfeilers der Ausgangspunkt: aktuelle Governance-Oberfläche prüfen, die bestehende Papier-RACI gegen die abgeleitete laufen lassen, Abweichungen zählen, entscheiden, welcher der vier Mechanismen die meiste Compliance-Wirkung zuerst liefert. RoPA-Erzeugung ist die übliche Antwort — sie spart dem DSB eine halbe Woche manueller Pflege pro Monat, und die Frische-Verbesserung ist das Artefakt, das ein Aufseher zuerst bemerkt.


Wenn Sie einen der vier Mechanismen selbst verdrahtet haben — oder auf ein spezifisches Projektions-Problem gestoßen sind, das die obige SQL nicht abdeckt — würde ich wirklich gerne hören, wo die Nähte sichtbar wurden. Die Tür steht offen unter cordata.tech/contact. Weiterlesen: der Data-Governance-Grundpfeiler für das Betriebs-Modell-Argument, das dieser Walkthrough konkretisiert; die Fabric + Mesh auf AWS-Referenz für das LakeFormation + DataZone-Fundament, aus dem die Projektionen lesen; und der kommende Beitrag DSGVO Art. 17 über ein Mesh hinweg zur Löschungs-Mechanik, die dieser Walkthrough nicht abdeckt.