# 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`](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 eine fail-closed, LLM-faehige Offenlegungsprojektion und kein Rohdaten- oder Kundenkontextspeicher. Seit Disclosure Policy 2.1.0 duerfen regulaere kanonische Findings und GPO-Vergleiche keinen personen- oder kundenidentifizierenden Inhalt transportieren. Bei den explizit bekannten Scannerprofilen CIS-CAT, Maester, PingCastle und Purple Knight bleiben jedoch regelbezogene Titel, Beschreibungen und Empfehlungen erhalten, damit die Result DB fachlich und fuer ein LLM nutzbar bleibt. Vor der Ausgabe ersetzt die Projektion exakte private Begleitwerte und feste Identifierklassen durch typisierte Platzhalter und wandelt HTML-Regelprosa in Klartext um. Assets, Personen-/Reviewfelder, Tenant-/Host-/GPO-/Backupidentitaeten, Locators, Quellzeitpunkte und interne Artefaktprovenienz bleiben ausschliesslich im internen Analysepfad. Unbekannte Werkzeuge und generische Quellen fallen weiterhin auf kontrollierte Bezeichnungen ohne Quellprosa zurueck. Disclosure Policy 2.5.0 erweitert die freigegebenen PingCastle-Rule-IDs auf den oeffentlich dokumentierten Regelkatalog. Die Validierung ist gross-/kleinschreibungsunabhaengig, die Ausgabe behaelt die native Schreibweise; unbekannte IDs bleiben aus der LLM-faehigen Result DB ausgeschlossen. Disclosure Policy 2.5.0 zieht fuer GPO-Vergleiche die verbindliche Grenze zwischen technischer Abweichung und Quellenidentitaet. Titel, technische Setting-ID, Policy-Pfad, Value Name, Operation, Registry-Typ, konfigurierter Wert, erwarteter Wert, Comparator-Relation, Status, oeffentliche Baselinebezeichnung/-version, oeffentliche Beschreibung/Empfehlung, belegte Diagnosen, Verarbeitungsstaende und die Aussagegrenze des Backupvergleichs duerfen vollstaendig erscheinen. Kunden-/Projektname, Domainlabel, kundenspezifischer GPO-Name, GPO-/Backup-GUID, Datei-/Sharepfad, interne Projekt-/Assessment-/Run-/Import-/Artefaktidentitaet, Source Locator, Zeitpunkte und Review-/Risikoakzeptanzdaten bleiben ausgeschlossen. `policyIdentityKey` und interne Baseline-IDs werden nicht ausgegeben; die technische Setting-ID steht ausschliesslich als kontrollierte `Source Rule ID`. Automatisch erzeugte `gpo-policy-identity-*`-Hashes sind niemals oeffentliche Regelidentitaeten. Wohlbekannte oeffentliche Principals wie `S-1-5-18`, `S-1-5-32-544` und `BUILTIN\\Administrators` bleiben technisch lesbar. Domain-SIDs verlieren ihre eindeutigen Domainkomponenten (`S-1-5-21-[DOMAIN]-`), bekannte Domainrollen werden als `[DOMAIN]\\` dargestellt und sonstige konkrete Benutzer/Gruppen erhalten stabile, nur innerhalb des einzelnen Resultats gueltige Platzhalter `[DOMAIN_PRINCIPAL_n]`. Dieselbe Reduktion gilt rekursiv fuer Baselinevarianten. Die Originalzuordnung bleibt ausschliesslich in Work und GPO Analysis erhalten. Die fachlichen Bewertungsachsen bleiben erhalten: normalisierte Severity und Outcome, Result-Typ, Abweichung/IOE, kontrollierte GPO-Klassifikation, Comparator-Relation sowie nicht identifizierende Aggregate und technische Versionsmetadaten. Damit bleibt die Sicherheitsbewertung fuer ein LLM nutzbar, ohne Kunden- oder konkrete GPO-Quellenidentitaet weiterzugeben. Die Aussage bezieht sich auf Identifizierbarkeit; ein Finding beschreibt weiterhin den anonymisierten Sicherheitszustand des aktiv ausgewaehlten Scopes. 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 2.0.0 und Disclosure Policy 2.5.0. Er wendet die allgemeine Projektion nochmals an, verlangt fuer jede Environment-Zeile weiterhin eine Disclosure-Klassifikation und entfernt zusaetzlich interne Result-, Projekt-, Assessment-, Run-, Snapshot-, Evaluation- und Artefaktidentitaeten sowie Zeitpunkte. Manifest-Scope und Filter werden nur als Boolesche beziehungsweise Mengenangaben ausgegeben; Suchtext, Assetfilter und konkrete Scope-IDs werden nie serialisiert. Disclosure Policy 2.6.0 (2026-09-28) schliesst eine Luecke zur obigen Grenze: `reviewStatus`, `reviewDisposition` (einschliesslich akzeptierter Risiken) und `reviewPriority` gelangten zuvor in Result DB API und JSONL. Die Projektion gibt jetzt nur noch Felder aus, die in `RESULT_DB_FIELD_DISCLOSURE` ausdruecklich als `disclose` klassifiziert sind; ein neues Projektionsfeld verhindert die Kompilierung, bis es klassifiziert ist, und zur Laufzeit hinzugefuegte Felder fallen weg. Review-Status und -Disposition sind deshalb keine Result-DB-Filter mehr; Work und die interne Analyse behalten den Review unveraendert. JSONL-Schema 2.1.0 ergaenzt im Manifest `handling` mit der Klassifikation `confidential-security-posture`: Die Result DB ist anonymisiert, beschreibt aber weiterhin ausnutzbare Schwaechen der bewerteten Umgebung und ist bei Weitergabe an externe LLM-Anbieter oder Dritte entsprechend zu behandeln. Disclosure Policy 3.0.0 und JSONL-Schema 3.0.0 (2026-09-28, verbindliche Nutzeranforderung, ersetzt die Entscheidung vom 2026-09-25 zum vollstaendigen Ist-/Sollwert): Die Result DB ist datenschutzkonform und keine Angriffsanleitung. Sie enthaelt keine Werte, die die bewertete Umgebung wiedererkennbar machen oder den Angriffsaufwand kalibrieren. Der GPO-Istwert samt Principals, die Anzahl der GPO-Quellen und konkrete Baseline-Releases entfallen; es bleiben technische Setting-ID, Sollwert, Comparator-Relation und die Baseline-Familie. Identische Baseline-Erwartungen werden zusammengefasst. In Scannerprosa ausser CIS-CAT werden exakte Anzahlen, Datums-, Zeit- und Dauerangaben zu `[count]`, `[date]`, `[time]` und `[duration]`; oeffentliche technische Kennungen (Versionen, CVE/KB, Regel- und ATT&CK-IDs, Produktnamen, Ports, Event-IDs, oeffentliche SIDs) bleiben. CIS-CAT-Prosa ist Benchmarktext und behaelt ihre oeffentlichen Empfehlungswerte. Die Zusatzbeobachtungs-Policy 2.0.0 laesst nur noch binaere Konfigurationszustaende zu; Anzahlen, Scores, Funktionsebenen und Versionen bleiben intern. Das JSONL-Manifest enthaelt statt `generatedAt` nur `generatedOn` (Kalendertag), der Dateiname nur das Datum. Work und interne Analyse behalten alle Originalwerte. Disclosure Policy 3.1.0 (2026-09-28, Nutzerkorrektur nach Pruefung des Dummy-Exports) begrenzt die Schwaerzung auf echte Umgebungswerte: Nummerierte Schritte bleiben; nur das Empfehlungsfeld behaelt Hersteller-Schwellenwerte (`at least 8 characters`, `more than 180 days`, `15 or more`), Titel und Beschreibung bleiben vollstaendig geschwaerzt. Gruppenrichtlinien- und Registry-Pfade sowie dokumentierte Windows-Autoritaeten (`NT SERVICE`, `RESTRICTED SERVICES` u. a.) gelten nicht als Konten. `[ip]` betrifft nur alleinstehende IPv4-Adressen, nie CIS-Regelnummern, `Rule`-Verweise oder dokumentierte Bereichsgrenzen. Der Handling-Hinweis nennt jetzt ausdruecklich, dass oeffentliche Baseline-Erwartungen, Herstellerhinweise und Schwellenwerte erhalten bleiben. Disclosure Policy 3.2.0 (2026-09-28, Nutzerkorrektur nach Pruefung des gefilterten Dummy-Exports) erhaelt Inline-Aufzaehlungen (Folge ab 1 mit mindestens zwei Punkten, eine wiederholte oder uebersprungene Quellnummer toleriert), dokumentierte Attributflags (`adminCount=1`) und im Empfehlungsfeld Zielwerte (`set to 1`). Links und Hostnamen von Hersteller- und Standard- dokumentation bleiben, solange sie weder `@`, Tenant-Merkmale (`onmicrosoft`, `sharepoint`), private Literale noch weitere nicht oeffentliche Hosts enthalten. Tenant-Hosts und alle uebrigen Hosts bleiben geschwaerzt. Dateinamen, ScubaGear-Kennungen, Graph-Berechtigungen, Code-Namespaces und -Bezeichner sowie Satzverbindungen ohne Leerzeichen gelten nicht als Domain. Gespeicherte Result DB (2026-09-28, verbindliche Nutzeranforderung, Migration 024): Die Zeilen werden beim Erzeugen offengelegt, geprueft und in `assessor_result_db_rows` gespeichert, mit Statussatz `assessor_result_db_state` (Revision, Policy- und Waechterversion, Zeilen, Ersetzungen, gesperrte Zeilen). Der Aufbau laeuft nach dem Start und nach jeder gespeicherten Aenderung; Lesezugriffe warten auf einen aktuellen Stand. Jede Zeile traegt `disclosureTrace` (Feld, Regel, Anzahl, zurueckgehaltene Felder) ohne Originalwerte. Der Identitaetswaechter 1.3.0 entfernt die eigenen Namen des Clients aus allen Zeilen und sperrt ein Feld, wenn ein Name in anderer Schreibweise ueberlebt. `GET /api/v1/results/status` liefert den Pruefstand; die Oberflaeche zeigt ihn und im Zeilendetail das Protokoll mit `Open original in Work`. ### 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.