25 KiB
RESULT_CONTRACT
schema_version : 1.0.0
status : verbindlicher Datenvertrag
zielort : docs/RESULT_CONTRACT.md
gilt_fuer : Purple Knight, PingCastle, Maester, CIS-CAT Pro, GPO-/Baseline-Analyse
konsumenten : ASSESSOR UI, lokales LLM, Exporte, weitere Tools
Dieser Vertrag definiert die oberste Klassifikationsschicht und die Graph-Grundlage des Canonical Result Store. Er ist ein versionierter Integrationsvertrag, kein reiner UI-Index. Alle Konsumenten greifen auf dieselbe Semantik zu und müssen nicht wissen, welche native Spalte ein Tool verwendet hat.
Der Vertrag ersetzt IOE als Organisationsprinzip durch result_type. IOE bleibt vollständig erhalten, wird aber zu einer abgeleiteten, indizierten Facette. Alle Zusagen des PingCastle-Arbeitspakets (Dokument 2) für Purple Knight, PingCastle und Maester bleiben unverändert gültig.
Realisierungshinweis: Dieser Vertrag wurde additiv auf das bereits bestehende ASSESSOR-Datenmodell abgebildet (Pfad A, Integrationsentscheidung 2026-07-23). Siehe Abschnitt „Abweichungen der ASSESSOR-Realisierung" am Ende dieses Dokuments für die Stellen, an denen die konkrete Umsetzung von diesem Vertrag abweicht, und warum. Der fachlich verbindliche Umsetzungsstand ist
docs/CANONICAL_MODEL.md; dieses Dokument bleibt der unveränderte externe Vertragstext.
Grundprinzip: zwei orthogonale Achsen
Der bisherige Konflikt entsteht, weil IOE zwei verschiedene Fragen vermischt hat. Der Vertrag trennt sie strikt.
result_type beschreibt die Art der Aussage, also die Quellsemantik. Geschlossenes, versioniertes Enum. Stabil.
result_state beschreibt den Ausgang der Aussage, normalisiert und typübergreifend. Dies ist die eine Achse, auf der toolübergreifend sicher gefiltert wird.
Jede weitere Dimension (Severity, IOE, Score) hängt an diesen beiden Achsen, ersetzt sie aber nicht.
result_type (geschlossenes Enum)
exposure_finding Exposure-Scanner. Aussage: Exposure vorhanden oder nicht.
Quellen: Purple Knight, PingCastle RiskRules, Maester Security Tests.
Nur dieser Typ kann per Default zu IOE werden.
compliance_check Benchmark-Pruefung gegen eine Empfehlung. Aussage: konform oder nicht.
Quelle: CIS-CAT (XCCDF).
policy_deviation Setting-Vergleich gegen eine Baseline. Aussage: Sollwert vs. Istwert plus Verdikt.
Quelle: GPO-Backup vs. MS Security Baseline.
informational Nicht-assertive Kontext- oder Inventardaten ohne Pass/Fail-Semantik.
unknown Rueckfallwert, wenn die Quellsemantik nicht klassifizierbar ist.
Bewusst kein eigener Typ: technical_error ist ein Zustand (result_state = error), keine Art von Aussage. Eine CIS-Prüfung, die nicht ausgeführt werden konnte, bleibt result_type = compliance_check mit result_state = error. Das hält result_type stabil.
result_state (normalisiert, typübergreifend)
fail negativer Ausgang (Exposure erkannt / Check nicht bestanden / Setting weicht ab)
pass positiver Ausgang
warning
error Auswertung oder Ausfuehrung fehlgeschlagen
not_applicable
not_evaluated
informational
unknown
Dies ist die einzige Achse, auf der ein typübergreifender „alle Fails"-Filter zuverlässig arbeitet. Nativwerte bleiben parallel im typspezifischen Block erhalten und werden nie durch die Normalisierung überschrieben.
is_ioe (abgeleitete Facette)
is_ioe wird nicht aus der Quelle übernommen, sondern per expliziter, versionierter Regel abgeleitet und als materialisierte, indizierte boolesche Spalte gespeichert. Nicht in JSONB.
Ableitungsregeln:
exposure_finding + exposure_state = detected -> is_ioe = true (Default)
compliance_check -> is_ioe = false (Default)
policy_deviation -> is_ioe = false (Default)
Nur eine explizite, versionierte Elevationsregel darf einen einzelnen Compliance-Check oder eine Policy-Abweichung auf IOE heben. Niemals per Default.
Jede Ableitung trägt ioe_rule_id, ioe_rule_version, ioe_derivation_method.
Äquivalenz zum bisherigen Filter aus Dokument 2: findingType = ioe AND exposureState = detected entspricht jetzt is_ioe = true. Der Feinfilter über not_detected, not_evaluated, execution_error läuft weiterhin über exposure_state, das nun im exposure-Block sitzt und nur für exposure_finding existiert.
Quellen-Mapping
| Quelle | Nativwert | result_type | Zustandsfeld (Block) | result_state | is_ioe |
|---|---|---|---|---|---|
| Purple Knight | IOEs found | exposure_finding | exposure_state = detected | fail | true |
| Purple Knight | No IOE found | exposure_finding | exposure_state = not_detected | pass | false |
| PingCastle | RiskRule Points > 0 | exposure_finding | exposure_state = detected | fail | true |
| PingCastle | RiskRule Points = 0 | exposure_finding | exposure_state = not_detected | pass | false |
| PingCastle | nicht auswertbar | exposure_finding | exposure_state = not_evaluated | not_evaluated | false |
| PingCastle | Ausfuehrungsfehler | exposure_finding | exposure_state = execution_error | error | false |
| Maester | Security Test Failed | exposure_finding | exposure_state = detected | fail | true |
| Maester | Passed | exposure_finding | exposure_state = not_detected | pass | false |
| Maester | Diagnose / nicht ausgefuehrt | informational | – | not_evaluated | false |
| CIS-CAT | XCCDF fail | compliance_check | xccdf_result = fail | fail | false |
| CIS-CAT | XCCDF pass | compliance_check | xccdf_result = pass | pass | false |
| CIS-CAT | XCCDF error | compliance_check | xccdf_result = error | error | false |
| CIS-CAT | XCCDF notapplicable | compliance_check | xccdf_result = notapplicable | not_applicable | false |
| CIS-CAT | XCCDF notchecked / notselected | compliance_check | xccdf_result = notchecked | not_evaluated | false |
| CIS-CAT | XCCDF informational | compliance_check | xccdf_result = informational | informational | false |
| GPO vs. Baseline | Abweichung | policy_deviation | verdict = deviation | fail | false |
| GPO vs. Baseline | Uebereinstimmung | policy_deviation | verdict = match | pass | false |
| GPO vs. Baseline | Setting fehlt beim Subjekt | policy_deviation | verdict = missing_on_subject | informational | false |
| GPO vs. Baseline | Setting nur beim Subjekt | policy_deviation | verdict = extra_on_subject | warning | false |
| GPO vs. Baseline | nicht vergleichbar | policy_deviation | verdict = not_comparable | unknown | false |
Vorbehalte: Welche Maester-Tests als Exposure zählen, entscheidet das versionierte Profil (Dokument 2, Abschnitt 8). Ob ein fehlendes Setting als fail oder not_applicable gilt, ist settingabhängig und wird im GPO-Submodell festgelegt, nicht hier. Die XCCDF-Zustände notchecked, notapplicable und error dürfen nicht kollabiert werden.
Typspezifische Detailblöcke
Der Nativreichtum jeder Quelle bleibt in einem typspezifischen Block erhalten. Die normalisierten Top-Level-Felder sind der Vertrag; die Blöcke sind die Wahrheit der Quelle.
exposure exposure_state, score_original, score_unit, native_severity
compliance xccdf_result, benchmark_id, rule_id, profile_id, remediation
deviation setting_key (kanonisch), baseline_value, subject_value, verdict,
baseline_ref, baseline_version
Der deviation-Block ist hier nur in seiner Rolle definiert. Sein vollständiges Vergleichs-Submodell inklusive Setting-Identität und Verdikt-Logik ist noch offen (siehe Abschnitt Offene Punkte).
Kanonische Asset-Schicht: CANONICAL_ASSET
Ein Knoten wird ausschließlich über einen autoritativen, stabilen Schlüssel identifiziert. Anzeigenamen führen niemals zu einer Knotenzusammenführung. Dies ist die „keine Textähnlichkeit"-Regel aus Dokument 2, angewandt auf die Knotenidentität. Ohne diese Regel zerfällt der Graph in Dubletten oder verschmilzt falsch. Beides zerstört jede Blast-Radius-Aussage.
CANONICAL_ASSET
────────────────────────────────
asset_id internes Surrogat-PK
asset_key deterministischer Dedup-Schluessel, namespaced
asset_key_kind sid | object_guid | gpo_guid | domain_sid | trust_key | synthetic
asset_type user | group | computer | domain_controller | domain
| ou | gpo | trust | tenant | service_principal | unknown
primary_name Anzeigename, nicht autoritativ
domain
key_confidence 1.0 bei autoritativem Schluessel, < 1.0 bei synthetic
resolution_method wie der Schluessel gewonnen wurde
source_tools welche Tools diese Entitaet gemeldet haben
first_seen_at
last_seen_at
original_fields jsonb
extensions jsonb
Schlüsselpriorität, absteigend autoritativ:
objectSID Security Principals (user, group, computer, DC) autoritativ
objectGUID alle AD-Objekte, stabil ueber Umbenennung autoritativ
gpo_guid GPOs (der {GUID}-Ordner im Backup, gpreport) autoritativ
domain_sid Domaenenknoten autoritativ
trust_key Trust = (Quell-Domaenen-SID, Ziel-Domaenen-SID) autoritativ
synthetic nur-Name-Meldung ohne SID/GUID NICHT autoritativ
Merge-Regel (hart): Zwei Zeilen werden nur zusammengeführt, wenn sie denselben autoritativen Schlüssel teilen. Namensgleichheit löst niemals einen Merge aus.
Konservative Wahl für synthetic (in Version 1.0.0 verbindlich): Ein synthetischer Knoten verschmilzt nie automatisch mit einem autoritativen Knoten, auch nicht bei exaktem Namens- und Domänen-Match. Eine Bindung erfolgt ausschließlich über eine explizite, versionierte, protokollierte Rekonziliationsregel. Das erzeugt sichtbare Dubletten, bis eine solche Regel greift; diese Dubletten sind auditierbar und werden einer stillen Fehlverschmelzung vorgezogen. Kein erfundener SID.
Kantenschicht: ASSET_EDGE
Kanten sind eine eigenständige Entität, getrennt von Findings. Ein Finding ist ein Knoten-Attribut; eine Kante ist eine Beziehung zwischen zwei Knoten. Diese Trennung ist die Voraussetzung dafür, dass ein Attack Path überhaupt berechenbar wird.
ASSET_EDGE
────────────────────────────────
edge_id
source_asset_id FK CANONICAL_ASSET
target_asset_id FK CANONICAL_ASSET
edge_type siehe Katalog
direction gerichtet: source -> target
edge_source Tool + Import-Artefakt, das die Kante erzeugt hat
collection_method z. B. pingcastle-html-trust-table, gpo-backup-dacl
collector_version
source_locator
mapping_method
mapping_rule_version
confidence
first_seen_at
last_seen_at
properties jsonb kantentyp-spezifische Attribute
original_fields jsonb
edge_source, collection_method und collector_version sind verpflichtend. Sie sind die Grundlage der ehrlichen Blast-Radius-Deklaration: Nur wenn jede Kante ihre Herkunft trägt, ist ableitbar, welche Kantentypen erhoben wurden und welche fehlen.
Kantentyp-Katalog
Der Katalog ist selbst versioniert und Teil des Schemas. „Nicht erhoben" ist damit wohldefiniert: Katalog minus erhobene Typen.
| edge_type | Quelle im Bestand | Stufe | Status |
|---|---|---|---|
| MemberOf | PingCastle / PK Detailtabellen | A | extrahierbar |
| Trusts | PingCastle Trust-Bericht | A | extrahierbar |
| AllowedToDelegate | PingCastle (Constrained Delegation) | A | extrahierbar |
| AllowedToActOnBehalfOf | PingCastle (RBCD) | A | extrahierbar |
| GpLink | GPO-Backup / gpreport | A | extrahierbar |
| Contains (OU -> Objekt) | GPO-Backup / gpreport | A | teilweise |
| GPO-DACL (WriteDacl, WriteOwner, GenericAll, GenericWrite) | GPO-Backup ACL | A | extrahierbar |
| HasSession | – | B | nicht in den Quellen |
| AdminTo (lokaler Admin) | – | B | nicht in den Quellen |
| CanRDP / CanPSRemote / ExecuteDCOM | – | B | nicht in den Quellen |
| Objekt-DACL (alle Objekttypen) | – | B | nicht in den Quellen |
Die Stufe-B-Zeilen sind die BloodHound-/SharpHound-Kanten, aus denen die meisten realen Pfade entstehen. Sie sind der Grund, warum Stufe A eine Angriffsflächen-Topologie liefert, aber noch keinen vollständigen Attack Path. Ihre Ingestion ist eine eigenständige, noch nicht eingeplante Datenerfassung.
Finding zu Graph-Rolle
Nicht jedes Finding ist eine Kante. Die Abbildung erfolgt über eine versionierte Regel-Tabelle in genau drei Rollen und wird nie per Import erfunden.
Knoten-Eigenschaft: AS-REP-Roastable (DONT_REQ_PREAUTH), Kerberoastable (SPN gesetzt), Unconstrained Delegation, „Passwort läuft nie ab". Das sind Fähigkeiten eines Knotens, keine Beziehungen. Sie landen als Property am CANONICAL_ASSET, referenziert auf das erzeugende Finding.
Kante: RBCD erzeugt eine AllowedToActOnBehalfOf-Kante, Constrained Delegation eine AllowedToDelegate-Kante, eine GPO-ACL-Berechtigung eine GPO-DACL-Kante. Diese Findings haben strukturelle Bedeutung im Graphen.
Reines Overlay: Ein CIS-Härtungs-Fail oder eine Policy-Abweichung sitzt auf einem Knoten (etwa einem DC oder GPO), erzeugt aber weder Kante noch angriffsrelevante Eigenschaft. Kontext auf dem Knoten.
Die Zuordnung Finding-Typ zu Graph-Rolle trägt mapping_rule_version und bleibt damit prüfbar und austauschbar, analog zur Score-to-Severity-Sperre.
EDGE_COVERAGE und ehrlicher Blast Radius
Ein Blast Radius über eine unvollständige Kantenmenge unterschätzt die reale Erreichbarkeit. Ihn als vollständig darzustellen wäre ein Prüffehler. Deshalb wird die Kantenabdeckung explizit modelliert.
EDGE_COVERAGE
────────────────────────────────
assessment_id / scan_run_id
edge_type
edge_source
collected true | false
collector_version
Jede Erreichbarkeits- oder Blast-Radius-Berechnung läuft über einen deklarierten Kantentyp-Filter und liefert das Ergebnis mit zwei Pflichtangaben: computed_over_edge_types und uncollected_edge_types (Katalog minus erhoben). Jede Graph-Aussage trägt damit sichtbar, worauf sie beruht und was ihr fehlt.
Deterministische Graph-Operationen
Diese Operationen sind reine Graph-Algorithmen, damit assertionssicher und ohne Kennzeichnungspflicht.
Erreichbarkeit erreichbare Knotenmenge von X unter Kantenfilter E
Kuerzester Pfad zwischen Knoten A und B unter E
Transitive Huelle Blast Radius = erreichbare Menge von X unter E
Finding-Overlay welches Finding sitzt auf welchem Knoten
Nicht Teil dieser Schicht, weil heuristisch und kennzeichnungspflichtig: Pfad-Ranking, Risikobewertung eines Pfades, Blast Radius als Impact-Score statt roher Erreichbarkeitsmenge, das Verketten von ATT&CK-Techniken zu einer Angriffserzählung, sowie jede Cross-Tool-Knotenidentität, die auf etwas anderem als SID oder GUID beruht.
Enrichment: ATT&CK und ANSSI
MITRE ATT&CK und ANSSI-Härtungsstufen sind eine Taxonomie der Findings, kein Graph der Umgebung. Als Knoten- und Finding-Annotation sowie als Filter- und Gruppierungsachse sind sie deterministisch und zulässig. Als Pfadgenerator sind sie unzulässig, da das Verketten von Techniken eine generierte Angriffserzählung wäre.
Was nicht Knoten ist
Registry-Werte, einzelne Policy-Settings und CIS-Regeln sind keine Graph-Knoten. Sie bleiben Inhalt von Findings beziehungsweise des deviation-Blocks. Ein Policy-Deviation-Finding kann einen GPO referenzieren; der GPO ist ein Knoten, das Setting darin nicht.
Invarianten
1. result_type ist geschlossen und versioniert. Keine Quelle fuehrt Ad-hoc-Typen ein.
2. result_state wird abgeleitet, nie roh aus einem Quell-String oder Spaltennamen uebernommen.
3. is_ioe wird per expliziter, versionierter Regel abgeleitet; nie aus der Quelle uebernommen;
nie fuer compliance_check oder policy_deviation per Default true.
4. Nativwerte bleiben parallel vollstaendig erhalten (typspezifischer Block plus original_fields).
5. Jede Ableitung traegt mapping_method und mapping_rule_version.
6. Fehlend ist NULL bzw. unknown, nie erfunden.
7. Tooluebergreifend vergleichbar sind nur result_type, result_state, severity_normalized, is_ioe.
score ist tooluebergreifend nicht vergleichbar und darf typuebergreifend nicht sortiert
oder schwellwertgefiltert werden.
8. Knotenidentitaet nur ueber autoritativen Schluessel; Name fuehrt nie zu Merge.
9. synthetic-Knoten verschmelzen nie automatisch mit autoritativen Knoten.
10. Kanten sind erste Klasse, getrennt von Findings.
11. Jede Kante traegt edge_source, collection_method, collector_version.
12. Erreichbarkeit nur ueber deklarierten Kantentyp-Filter; Ergebnis nennt uncollected_edge_types.
13. Kanten sind gerichtet; Richtung ist semantisch.
14. Finding zu Graph-Rolle nur ueber versionierte Regel, nie per Import erfunden.
Filter-Semantik
[ IOE ] primaer: is_ioe = true. Semantisch unveraendert gegenueber Dokument 2.
[ Abweichung ] ASSESSOR-Ergaenzung, nicht Teil des Vertrags: is_ioe OR outcome = warning.
[ result_type ] oberste Facette: Exposure / Compliance / Policy / Informational.
[ result_state ] typuebergreifend: Fail / Pass / Warning / Error / N/A / Not evaluated.
[ Severity ] orthogonal, kategorial, typuebergreifend.
Typspezifische Facetten: exposure_state nur bei Exposure, xccdf_result nur bei Compliance, verdict nur bei Policy. Alle Facetten sind kombinierbar.
Beispiele:
is_ioe = true
is_ioe = true AND source_tool = pingcastle
is_ioe = true AND severity_normalized = high
result_type = compliance_check AND result_state = fail
result_type = policy_deviation AND verdict = deviation
result_state = fail
Verhältnis zu bestehenden Dokumenten
Additiv. result_type, result_state und is_ioe ergänzen die dokumentierte Trennung von Originalwert und normalisiertem Wert (CANONICAL_MODEL.md). RESULT_ASSET referenziert künftig canonical_asset_id statt Namen inline zu speichern; dies ist der einzige Eingriff in die bestehende Struktur und zugleich der Punkt, an dem Deduplizierung entsteht. Der gesamte PingCastle-Extraktions- und Verknüpfungspfad aus Dokument 2 bleibt unverändert und speist Knoten und Stufe-A-Kanten aus den bereits extrahierten Detailtabellen.
Offene Punkte (nicht Teil von 1.0.0)
- GPO-/Baseline-Vergleichs-Submodell: Setting-Identitaet, Verdikt-Logik,
Behandlung von "nicht konfiguriert" vs. "abwesend", baseline_ref-Versionierung.
- CIS-CAT-Adapter: Festlegung XCCDF/ARF vs. JSON nach Verifikation der Lizenzausgabe.
- Elevationsregeln, die einzelne Compliance-/Policy-Befunde auf is_ioe = true heben.
- Rekonziliationsregeln fuer synthetic-Knoten (Bindung an autoritative Knoten).
- Stufe B: Ingestion von BloodHound-/SharpHound-Klasse-Kanten (Session, AdminTo, DACL).
- Mandanten-/Isolationsmodell ueber project_id hinaus (Row-Level Security o. ae.).
Changelog
1.0.0 Erstfassung. result_type als oberster Diskriminator, result_state normalisiert,
is_ioe als abgeleitete indizierte Facette. Typspezifische Bloecke (exposure,
compliance, deviation). Asset-Schicht (CANONICAL_ASSET) mit autoritativer
Schluesselprioritaet und konservativer Merge-Regel. Kantenschicht (ASSET_EDGE),
Kantentyp-Katalog, EDGE_COVERAGE, deterministische Graph-Operationen.
Abweichungen der ASSESSOR-Realisierung
Dieser Abschnitt ist kein Teil des externen Vertrags, sondern dokumentiert, wo und warum die konkrete Umsetzung in ASSESSOR von diesem Text abweicht. Grundlage ist die Integrationsentscheidung „Pfad A" vom 2026-07-23: additive Abbildung auf das bestehende ASSESSOR-Datenmodell statt einer zweiten, parallelen Resulttabelle.
Abweichung: eigenes Praedikat neben is_ioe, nicht statt is_ioe
is_ioe bleibt exakt wie im Vertrag definiert: exposure_finding mit
exposure_state = detected, ohne Elevationsregel. Die Arbeitsansichten brauchen
darueber hinaus eine breitere Frage — „verlangt dieses Ergebnis Aufmerksamkeit?" —,
weil ein Werkzeug ein Ergebnis melden kann, das weder bestanden noch fehlgeschlagen
ist. Maester meldet dafuer Investigate, ASSESSOR normalisiert das auf
outcome_normalized = warning.
ASSESSOR fuehrt dafuer ein eigenes versioniertes Praedikat
(src/domain/deviation-predicate.ts, Version 1.0.0):
is_deviation := is_ioe OR outcome_normalized = 'warning'
Es ist nicht persistiert, sondern wird bei der Projektion abgeleitet; die
PostgreSQL-Vorfilterung bildet dieselbe Formel ab. Es liest ausschliesslich bereits
normalisierte Achsen und gilt damit ohne Aenderung fuer jedes weitere Werkzeug.
Ausdruecklich nicht enthalten sind pass, not_run, not_applicable und
execution_error; ein Ausfuehrungsfehler ist ein technisches Problem des Laufs und
hat mit technical_execution_state eine eigene Achse.
Offen bleibt, dass deriveExposureState fuer outcome = warning weiterhin
unknown liefert. Ein eigener Zustand (etwa indeterminate) wuerde den
Vertragsenum, die Check-Constraint aus Migration 007 und damit eine Migration samt
Versionssprung beruehren; er ist bewusst noch nicht umgesetzt.
result_state statt eigener Spalte: abgeleitete Projektion
ASSESSOR trennt seit Migration 007_unified_result_read_model.sql bewusst zwei
Dimensionen, die dieser Vertrag in einer Spalte result_state zusammenführt:
outcome_normalized— das fachliche Ergebnis (fail,pass,warning,informational,execution_error,not_run,not_applicable,not_evaluated,unknown)technical_execution_state— ob die Prüfung technisch überhaupt durchlief (completed,failed,not-run,unknown)
Begründung im Umsetzungsplan: „Ein technischer Parser-/Toolfehler ist kein
fachlich fehlgeschlagener Check." Ein result_state = error würde diese
Unterscheidung wieder aufheben. ASSESSOR führt deshalb keine physische
result_state-Spalte. Wo ein Konsument den Vertragswert braucht, wird er per
versionierter Regel aus beiden Bestandsachsen projiziert (projectResultState in
src/domain/result-contract-mapping.ts, RESULT_CONTRACT_MAPPING_VERSION).
Die Bestandsachsen bleiben die persistierte Wahrheit.
source_tool bleibt offen statt geschlossenes Enum
tool_id ist in ASSESSOR ein freier Wert, kein geschlossenes Enum. Grund ist der
generische Fallback (Leitprinzip „unbekannte Quellen müssen importierbar
bleiben"): ein geschlossenes source_tool-Enum würde jede noch nicht
katalogisierte Quelle auf unknown zwingen und die tatsächliche Tool-Kennung
verlieren. source_tool im Sinne dieses Vertrags ist daher eine Projektion über
tool_id, kein Speicherformat.
review_status bleibt zweidimensional
ASSESSOR trennt reviewStatus (Workflow-Zustand) und reviewDisposition
(Entscheidung bei Abschluss) mit eigener ReviewWorkflowStatus-Achse (7 Werte,
inkl. optimistischer Revision je Review). Der einwertige review_status dieses
Vertrags wird bei Bedarf daraus abgeleitet, nicht umgekehrt gespeichert.
Keine eigene canonical_result-Tabelle
result_type, is_ioe, ioe_rule_id, ioe_rule_version,
ioe_derivation_method sowie exposure_state sind additive Spalten auf der
bestehenden Occurrence-Tabelle (assessor_finding_occurrences,
Migration 008_result_contract_facets.sql), nicht Spalten einer neuen
Haupttabelle. result_compliance und result_deviation wurden nicht als neue
Haupttabellen angelegt. GPO-/Baseline-Vergleiche stammen aus den versionierten
Policy-Evaluation-Entitäten und werden nur im Read-Modell vereinheitlicht.
Datenschutzgrenze der Result DB
Die Result DB ist kein Rohdaten- oder Kundenkontextspeicher. Kanonische Findings bleiben mit ihren allgemeinen Regeltexten, Bewertungen und Frameworkreferenzen sichtbar; verknuepfte Results, originale Tabellenfelder und vollstaendige Scannerkontexte bleiben ausserhalb dieser Projektion erhalten.
Ergaenzende Scannerbeobachtungen durchlaufen vor der Unified-Result-Projektion
die versionierte, fail-closed Disclosure Policy
result-db-supplemental-disclosure 1.0.0. Eine Beobachtung wird nur ausgegeben,
wenn Werkzeug, Property und ein enger Datentyp-/Wertebereich ausdruecklich
freigegeben sind. Titel, Subject-Klasse und Property werden aus der kontrollierten
Regel neu aufgebaut. Subject-Identitaet, Originalwert, Attribute, betroffener
Assetname, Abschnitt und Source Locator werden niemals aus der Beobachtung in
die Result DB uebernommen. Unbekannte Werkzeuge und neue Zusatzfelder bleiben
vollstaendig in der internen Persistenz, sind aber bis zu einer bewussten
Klassifikation nicht Teil der Result DB. Das betrifft nicht ihre regulaeren
kanonischen Findings.
Rohe GPO-Policy-Beobachtungen sind ebenfalls kein Result-DB-Inhalt. Sie bleiben
im expliziten internal-analysis-Lesemodus fuer Work und GPO Analysis erhalten;
versioniert evaluierte Soll-/Ist-Vergleiche erreichen die normale Result DB als
policy_deviation. Ohne freigegebene Risikoregel bleibt die Severity unknown;
die Finding Factory darf nur belegte Ergebnisse anreichern und erzeugt keine
doppelte Result-Zeile. Der JSONL-Export verwendet Schema 1.1.0, verlangt fuer jede
Environment-Zeile eine Disclosure-Klassifikation und entfernt die gesperrten
Felder nochmals unmittelbar vor der Serialisierung.
Asset-/Kantenschicht: Struktur ohne Producer
CANONICAL_ASSET, ASSET_EDGE, ASSET_PROPERTY und EDGE_COVERAGE wurden
strukturgetreu als assessor_canonical_assets, assessor_asset_edges,
assessor_asset_properties und assessor_edge_coverage übernommen
(Migration 009_asset_graph.sql), mit text-Primärschlüsseln statt uuid
gemäß Bestandskonvention. In diesem Integrationsschritt existiert noch kein
Producer, kein Merge- und kein Rekonziliationscode — die Tabellen sind bewusst
leer nutzbar angelegt, nicht befüllt.