Assessor_2.0/docs/CANONICAL_MODEL_1.0.0.md

26 KiB

Hinweis: Dies ist der unveränderte externe Vertragstext „CANONICAL_MODEL 1.0.0", geliefert zusammen mit RESULT_CONTRACT.md und validiert gegen ein eigenständiges PostgreSQL-16-DDL. Er beschreibt ein vollständiges eigenständiges Datenmodell (u. a. eine eigene canonical_result- Haupttabelle, typspezifische Blöcke, Asset-/Kantenschicht) und ist nicht identisch mit dem tatsächlich implementierten ASSESSOR-Modell.

Der fachlich verbindliche, implementierte Stand ist docs/CANONICAL_MODEL.md (Schema-Version 1, src/domain/canonical-model.ts). Die Integrationsentscheidung vom 2026-07-23 (Pfad A, additiv) bildet die Neuerungen dieses Vertrags — result_type, is_ioe, die Asset-/Kantenschicht — auf das bestehende Modell ab, statt es zu ersetzen. Abweichungen und Begründungen stehen in RESULT_CONTRACT.md.

Diese Datei wird nicht mit docs/CANONICAL_MODEL.md zusammengeführt und dient ausschließlich als unveränderte Referenz auf den gelieferten Vertragsstand.


CANONICAL_MODEL

model_version   : 1.0.0
realisiert       : RESULT_CONTRACT 1.0.0
status           : vollstaendiges logisches Datenmodell, implementierungsreif
zielort          : docs/CANONICAL_MODEL.md
gilt_fuer        : Purple Knight, PingCastle, Maester, CIS-CAT Pro, GPO-/Baseline-Analyse
konsumenten      : ASSESSOR UI, lokales LLM, Exporte, Graph, weitere Tools

Dies ist die eine kanonische, filterbare Datenbasis. Sie ist so geschnitten, dass alle toolübergreifenden Filter ohne Join in typspezifische Blöcke oder JSONB funktionieren, und dass Graph- und GPO-Schicht ohne Vertragsänderung darauf aufsetzen können.

Das Modell trennt drei Verantwortungen: normalisierte Filterschicht (Haupttabelle), toolspezifische Wahrheit (typspezifische Blöcke plus original_fields), und Graph-Grundlage (Asset- und Kantenschicht). Fehlende Information bleibt NULL beziehungsweise unknown; sie wird nie erfunden oder aus ungeeigneten Feldern abgeleitet.

Überblick

PROJECT
└─ ASSESSMENT
   └─ SCAN_RUN
      ├─ IMPORT_ARTIFACT            (Quelldatei, SHA-256, Tool, Version)
      └─ CANONICAL_RESULT           (Haupttabelle, normalisierte Filterschicht)
         ├─ RESULT_EXPOSURE         (1:1, nur bei result_type = exposure_finding)
         ├─ RESULT_COMPLIANCE       (1:1, nur bei result_type = compliance_check)
         ├─ RESULT_DEVIATION        (1:1, nur bei result_type = policy_deviation)
         ├─ RESULT_CATEGORY         (1:N)
         ├─ RESULT_ASSET            (1:N, verknuepft mit CANONICAL_ASSET)
         ├─ RESULT_EVIDENCE         (1:N)
         ├─ RESULT_REMEDIATION_STEP (1:N)
         ├─ RESULT_REFERENCE        (1:N)
         └─ RESULT_REVIEW           (1:1, aktueller Stand)

Graph-Schicht (assessmentweit, referenziert Results):
CANONICAL_ASSET                     (deduplizierte Knoten)
├─ ASSET_PROPERTY                   (finding-abgeleitete Knoten-Eigenschaften)
ASSET_EDGE                          (gerichtete Kanten zwischen Knoten)
EDGE_COVERAGE                       (welche Kantentypen erhoben wurden)

Notation in den Feldlisten: PK Primärschlüssel, FK Fremdschlüssel, NN NOT NULL, IX indiziert (Filter- oder Join-relevant), enum X verweist auf den Enum-Katalog am Ende.

Scope-Tabellen

project
────────────────────────────────
id                 uuid         PK
key                text         NN   fachlicher Kundenschluessel
name               text
created_at         timestamptz  NN

assessment
────────────────────────────────
id                 uuid         PK
project_id         uuid         FK NN IX
name               text
period             text
created_at         timestamptz  NN

scan_run
────────────────────────────────
id                 uuid         PK
assessment_id      uuid         FK NN IX
label              text
run_kind           text         NN   import | evaluation
started_at         timestamptz
completed_at       timestamptz
created_at         timestamptz  NN

Import-Provenienz

import_artifact
────────────────────────────────
id                 uuid         PK
scan_run_id        uuid         FK NN IX
source_tool        enum source_tool   NN IX
source_tool_version text
source_format      enum source_format NN   html | xml | json | xccdf | arf | gpo_backup | unknown
file_name          text
file_sha256        text         NN IX   Dublettenerkennung
byte_size          bigint
extractor_id       text
extractor_version  text
profile_id         text
profile_version    text
imported_at        timestamptz  NN
parser_warnings    jsonb        Importdiagnostik
original_fields    jsonb

file_sha256 trägt die Dublettenerkennung. Ein bewusster Reimport erzeugt einen neuen scan_run-Kontext; bestehende Results werden nicht überschrieben.

Haupttabelle: CANONICAL_RESULT

Die normalisierte Filterschicht. Jede toolübergreifend vergleichbare Achse ist hier eine reale, indizierte Spalte. Kein Konsument muss in einen typspezifischen Block oder nach JSONB greifen, um zu filtern.

canonical_result
──────────────────────────────────────────────────────────
Identität
id                        uuid        PK
model_version             text        NN
project_id                uuid        FK NN IX
assessment_id             uuid        FK NN IX
scan_run_id               uuid        FK NN IX
import_artifact_id        uuid        FK IX
finding_definition_id     text        IX   stabile Regelidentitaet (z. B. RiskId)
finding_occurrence_id     text        IX   konkretes Auftreten
parent_result_id          uuid        FK IX   optionale Hierarchie

Herkunft
source_tool               enum source_tool     NN IX
source_tool_version       text
source_rule_id            text        IX   RiskId / PK-ID / XCCDF rule-id / setting_key
source_rule_name          text
source_locator            text        HTML-Selektor / XML-Pfad / Dateipfad
source_original_status    text        Nativwert, unveraendert
source_original_severity  text        Nativwert, unveraendert
source_original_category  text        Nativwert, unveraendert
source_original_score     text        Nativwert, unveraendert

Klassifikation  (Achse 1: Art der Aussage)
result_type               enum result_type   NN IX
is_ioe                    boolean     NN IX   abgeleitet, materialisiert
ioe_rule_id               text
ioe_rule_version          text
ioe_derivation_method     text

Bewertung  (Achse 2: Ausgang der Aussage)
result_state              enum result_state  NN IX
severity_normalized       enum severity      NN IX   default unknown
severity_rank              smallint    IX   fuer Sortierung innerhalb einer Severity-Skala
severity_original          text
severity_mapping_rule_id   text
severity_mapping_version   text
score_original             numeric
score_normalized            numeric     NICHT tooluebergreifend vergleichbar
score_max                   numeric
score_unit                  text

Inhalt
title                     text        NN
summary                   text
description               text
rationale                 text
technical_details         text
impact                    text
risk_statement            text
remediation_summary       text
remediation_details       text

Aggregierte Anzeigehilfen (denormalisiert fuer schnelle Listen)
affected_asset_summary    text
evidence_count            integer     NN default 0   IX
has_detailed_evidence     boolean     NN default false
primary_category_label    text        IX   gespiegelt aus RESULT_CATEGORY (is_primary)
primary_category_namespace text       IX

Quality / Provenance
recognition_confidence    numeric
mapping_confidence        numeric
classification_confidence numeric
severity_confidence       numeric
identity_confidence       numeric
evidence_completeness     numeric
mapping_method            text
mapping_rule_id           text
mapping_rule_version      text
extractor_id              text
extractor_version         text

Lifecycle
lifecycle_state           enum lifecycle_state  NN IX   default new
first_seen_at             timestamptz  NN
last_seen_at              timestamptz  NN
resolved_at               timestamptz
reoccurred_at             timestamptz
previous_result_id        uuid        FK
comparison_id             uuid

Zeit / Erweiterung
source_observed_at        timestamptz
imported_at                timestamptz
evaluated_at                timestamptz
created_at                 timestamptz  NN
extensions                 jsonb        bekannte, noch nicht kanonisierte Zusatzwerte
original_fields             jsonb        vollstaendige unveraenderte Quellfelder

Regel für die denormalisierten Anzeigehilfen (primary_category_label, affected_asset_summary, evidence_count): Sie sind gespiegelte Bequemlichkeitsfelder für schnelle Listenansichten. Die Wahrheit liegt in den Kindtabellen. Die Spiegelung erfolgt deterministisch beim Aufbau des Results, nicht in der UI.

Typspezifische Blöcke

Genau ein Block ist pro Result vorhanden, abhängig von result_type. Die Blöcke tragen die native Wahrheit der Quelle; die Haupttabelle trägt die normalisierte Projektion daraus.

result_exposure   (1:1, nur bei result_type = exposure_finding)
────────────────────────────────
result_id          uuid   PK FK
exposure_state     enum exposure_state  NN IX
                   detected | not_detected | not_evaluated | execution_error | unknown
native_severity    text
points_original     numeric              PingCastle
weight_original     numeric              Purple Knight
test_result_native  text                 Maester
result_compliance  (1:1, nur bei result_type = compliance_check)
────────────────────────────────
result_id          uuid   PK FK
xccdf_result       enum xccdf_result    NN IX
                   pass | fail | error | unknown | notapplicable | notchecked | notselected | informational | fixed
benchmark_id       text   IX
benchmark_version  text
rule_id            text   IX
profile_id         text
weight             numeric
check_content_ref  text
result_deviation   (1:1, nur bei result_type = policy_deviation)
────────────────────────────────
result_id          uuid   PK FK
setting_key        text   NN IX   kanonischer Policy-Schluessel, nie Anzeigetext
setting_namespace  text   NN      registry | privilege_right | system_access | audit | gptmpl | ...
baseline_value     text
subject_value      text
verdict            enum verdict         NN IX
                   match | deviation | missing_on_subject | extra_on_subject | not_comparable
baseline_ref       text   IX   Referenz auf das versionierte Baseline-Objekt
baseline_version   text   IX
gpo_asset_id       uuid   FK   betroffener GPO-Knoten, falls zutreffend

Der result_deviation-Block ist strukturell vollständig definiert, aber seine Vergleichs- und Verdikt-Logik (Setting-Identität pro Namespace, Behandlung von „nicht konfiguriert" versus „abwesend", Baseline-Registry) ist Gegenstand des noch offenen GPO-Submodells. Bis dahin bleibt der Block ungenutzt; das Modell ist bereits dafür ausgelegt.

Kindtabellen

result_category    (1:N)
────────────────────────────────
id                 uuid   PK
result_id          uuid   FK NN IX
namespace          text   NN IX   pingcastle | purple-knight | maester | cis | anssi | mitre | ...
label              text   NN IX
path               text           optionaler Hierarchiepfad
is_primary         boolean NN default false

Kategorien sind mehrwertig und namespaced. Eine Kategorie darf nie in eine Severity umgedeutet werden. is_primary markiert die Leitkategorie, die in canonical_result.primary_category_label gespiegelt wird.

result_asset       (1:N)   Verknuepfung Result zu Knoten
────────────────────────────────
id                 uuid   PK
result_id          uuid   FK NN IX
canonical_asset_id uuid   FK NN IX   dedupliziert, siehe Graph-Schicht
role               text           affected | subject | related
is_primary         boolean NN default false
source_locator     text

Betroffene Objekte werden nie als kommaseparierter String gespeichert, sondern als Verknüpfung auf deduplizierte Knoten. Das ist zugleich die Brücke zur Graph-Schicht.

result_evidence    (1:N)
────────────────────────────────
id                 uuid   PK
result_id          uuid   FK NN IX
evidence_type      enum evidence_type   NN
                   table | table_row | object | property | text | configuration
                   | command_output | policy_observation | reference | screenshot_reference | unknown
title              text
summary            text
structured_data    jsonb          strukturierte Detailzeile, nicht Freitext
text_value         text
source_view_id     text
source_locator     text   NN
sequence           integer NN     Reihenfolge bleibt erhalten

Detailzeilen dürfen nicht in eine große Textspalte gepresst werden. Jede Zeile behält ihren source_locator und ihre sequence.

result_remediation_step   (1:N)
────────────────────────────────
id                 uuid   PK
result_id          uuid   FK NN IX
sequence           integer NN
title              text
instruction        text
command             text
validation          text

Liefert ein Tool nur Remediation-Freitext, landet dieser in canonical_result.remediation_details; strukturierte Schritte bleiben leer und können später ergänzt werden, ohne den Originaltext zu überschreiben.

result_reference   (1:N)   Compliance und externe Referenzen
────────────────────────────────
id                 uuid   PK
result_id          uuid   FK NN IX
reference_type     text   NN   framework_control | mitre_attack | cve | cwe | vendor_doc | anssi | ...
namespace          text   IX   CIS | enterprise-attack | ...
reference_value    text   NN IX   z. B. T1078, 5.2.3
title              text
url                text

MITRE-ATT&CK- und ANSSI-Zuordnungen sind hier als Annotation abgelegt. Sie sind eine Taxonomie der Findings, keine Angriffskette.

result_review      (1:1, aktueller Stand)
────────────────────────────────
result_id          uuid   PK FK
review_status      enum review_status  NN default open   IX
review_disposition text
review_priority    text
review_owner       text
review_due_date    date
review_comment     text
accepted_risk_reason text
compensating_control text
updated_at         timestamptz

Reviewdaten sind strikt von Importdaten getrennt und verändern keine importierten Werte.

Graph-Schicht

CANONICAL_ASSET

Deduplizierter Knotenregister. Jede reale Entität erscheint genau einmal. Identität ausschließlich über autoritativen Schlüssel; Anzeigename führt nie zu einem Merge.

canonical_asset
────────────────────────────────
id                 uuid   PK
assessment_id      uuid   FK NN IX
asset_key          text   NN IX   deterministischer Dedup-Schluessel, namespaced
asset_key_kind     enum asset_key_kind   NN
                   sid | object_guid | gpo_guid | domain_sid | trust_key | synthetic
asset_type         enum asset_type       NN IX
                   user | group | computer | domain_controller | domain
                   | ou | gpo | trust | tenant | service_principal | unknown
primary_name       text           nicht autoritativ
domain             text   IX
key_confidence     numeric NN     1.0 bei autoritativem Schluessel, < 1.0 bei synthetic
resolution_method  text
source_tools       text[]
first_seen_at      timestamptz NN
last_seen_at       timestamptz NN
original_fields    jsonb
extensions         jsonb

UNIQUE (assessment_id, asset_key)

Merge-Regel (verbindlich in 1.0.0): Zusammenführung nur bei identischem autoritativem Schlüssel. synthetic-Knoten (nur-Name-Meldung ohne SID/GUID) verschmelzen nie automatisch mit autoritativen Knoten, auch nicht bei exaktem Namens- und Domänen-Match. Eine Bindung erfolgt ausschließlich über eine explizite, versionierte, protokollierte Rekonziliationsregel. Sichtbare Dubletten sind einer stillen Fehlverschmelzung vorzuziehen. Kein erfundener SID.

asset_property     (1:N)   finding-abgeleitete Knoten-Eigenschaften
────────────────────────────────
id                 uuid   PK
canonical_asset_id uuid   FK NN IX
property_key       text   NN IX   z. B. kerberoastable, asrep_roastable, unconstrained_delegation
property_value     text
source_result_id   uuid   FK      Finding, aus dem die Eigenschaft stammt
mapping_rule_version text

Fähigkeiten eines Knotens (Kerberoastable, AS-REP-Roastable, Unconstrained Delegation) sind Eigenschaften, keine Kanten. Die Zuordnung Finding zu Eigenschaft läuft über eine versionierte Regel.

ASSET_EDGE

Kanten sind erste Klasse, getrennt von Findings. Gerichtet, mit Pflicht-Provenienz.

asset_edge
────────────────────────────────
id                 uuid   PK
assessment_id      uuid   FK NN IX
source_asset_id    uuid   FK NN IX
target_asset_id    uuid   FK NN IX
edge_type          enum edge_type   NN IX
direction          text   NN   gerichtet: source -> target
edge_source        text   NN   Tool + Import-Artefakt
collection_method  text   NN
collector_version  text   NN
source_locator     text
mapping_method     text
mapping_rule_version text
confidence         numeric
first_seen_at      timestamptz NN
last_seen_at       timestamptz NN
properties         jsonb
original_fields    jsonb

edge_source, collection_method, collector_version sind verpflichtend. Sie ermöglichen die ehrliche Abdeckungsdeklaration.

Kantentyp-Katalog (Stufe A aus dem Bestand erhebbar, Stufe B noch nicht in den Quellen):

Stufe A   MemberOf, Trusts, AllowedToDelegate, AllowedToActOnBehalfOf,
          GpLink, Contains, GPO-DACL
Stufe B   HasSession, AdminTo, CanRDP, CanPSRemote, ExecuteDCOM, Objekt-DACL

EDGE_COVERAGE

Macht ehrlich sichtbar, welche Kantentypen erhoben wurden. Grundlage jeder korrekten Blast-Radius-Aussage.

edge_coverage
────────────────────────────────
id                 uuid   PK
assessment_id      uuid   FK NN IX
scan_run_id        uuid   FK IX
edge_type          enum edge_type   NN
edge_source        text
collected          boolean NN
collector_version  text

Jede Erreichbarkeits- oder Blast-Radius-Berechnung läuft über einen deklarierten Kantentyp-Filter und liefert computed_over_edge_types und uncollected_edge_types (Katalog minus erhoben) mit. Ein Blast Radius über eine unvollständige Kantenmenge unterschätzt die reale Erreichbarkeit und darf nie als vollständig dargestellt werden.

Enum-Katalog

result_type      exposure_finding | compliance_check | policy_deviation | informational | unknown
result_state     fail | pass | warning | error | not_applicable | not_evaluated | informational | unknown
severity         critical | high | medium | low | info | unknown
exposure_state   detected | not_detected | not_evaluated | execution_error | unknown
xccdf_result     pass | fail | error | unknown | notapplicable | notchecked | notselected | informational | fixed
verdict          match | deviation | missing_on_subject | extra_on_subject | not_comparable
lifecycle_state  new | persistent | resolved | reoccurred | changed | not_comparable | unknown
review_status    open | in_review | accepted | remediated | risk_accepted | false_positive | unknown
asset_type       user | group | computer | domain_controller | domain | ou | gpo | trust | tenant | service_principal | unknown
asset_key_kind   sid | object_guid | gpo_guid | domain_sid | trust_key | synthetic
edge_type        MemberOf | Trusts | AllowedToDelegate | AllowedToActOnBehalfOf | GpLink | Contains
                 | GPO-DACL | HasSession | AdminTo | CanRDP | CanPSRemote | ExecuteDCOM | ObjectDACL
evidence_type    table | table_row | object | property | text | configuration | command_output
                 | policy_observation | reference | screenshot_reference | unknown
source_tool      purple-knight | pingcastle | maester | cis-cat | gpo-analysis | unknown
source_format    html | xml | json | xccdf | arf | gpo_backup | unknown

Enums sind geschlossen und versioniert. Keine Quelle führt Ad-hoc-Werte ein; unklassifizierbares fällt auf unknown zurück.

Filteroberfläche

Die folgenden Spalten sind indiziert und bilden die vollständige toolübergreifende Filterebene. Kein Filter erfordert einen Join in einen typspezifischen Block oder JSONB.

canonical_result.result_type
canonical_result.result_state
canonical_result.is_ioe
canonical_result.severity_normalized
canonical_result.source_tool
canonical_result.lifecycle_state
canonical_result.primary_category_namespace
canonical_result.primary_category_label
canonical_result.evidence_count
result_category.namespace / label        (fuer Mehrfachkategorien)
result_review.review_status

Typspezifische Verfeinerung über einen bewussten Join:

result_exposure.exposure_state           nur exposure_finding
result_compliance.xccdf_result           nur compliance_check
result_deviation.verdict                  nur policy_deviation

Beispiel-Abfragen:

IOE, tooluebergreifend
  WHERE is_ioe = true

High-IOEs, tooluebergreifend
  WHERE is_ioe = true AND severity_normalized = 'high'

Alle CIS-Haertungsluecken
  WHERE result_type = 'compliance_check' AND result_state = 'fail'

Alle Policy-Abweichungen
  WHERE result_type = 'policy_deviation' AND verdict = 'deviation'

Alle Fails, tooluebergreifend, unabhaengig vom Typ
  WHERE result_state = 'fail'

Nur PingCastle-IOEs
  WHERE is_ioe = true AND source_tool = 'pingcastle'

Findings mit strukturierter Evidenz
  WHERE evidence_count > 0

Determinismus- und Provenance-Regeln

1.  result_type ist geschlossen und versioniert. Keine Ad-hoc-Typen.
2.  result_state wird abgeleitet, nie roh aus Quell-String oder Spaltenname uebernommen.
3.  is_ioe wird per expliziter, versionierter Regel abgeleitet und materialisiert;
    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.  Severity nur aus nativem Wert oder versionierter, freigegebener Mappingregel.
    Kategorie wird nie in Severity umgedeutet. Sonst severity_normalized = unknown.
7.  score ist tooluebergreifend nicht vergleichbar und darf typuebergreifend nicht
    sortiert oder schwellwertgefiltert werden.
8.  Fehlend ist NULL bzw. unknown, nie erfunden.
9.  Mehrwertige Daten (Kategorien, Assets, Evidenz, Referenzen, Schritte) nie als
    kommaseparierter String; immer relationale Kindtabelle.
10. Knotenidentitaet nur ueber autoritativen Schluessel; Name fuehrt nie zu Merge.
11. synthetic-Knoten verschmelzen nie automatisch mit autoritativen Knoten.
12. Kanten sind erste Klasse; jede Kante traegt edge_source, collection_method, collector_version.
13. Erreichbarkeit nur ueber deklarierten Kantentyp-Filter; Ergebnis nennt uncollected_edge_types.
14. Finding zu Graph-Rolle (Eigenschaft, Kante, Overlay) nur ueber versionierte Regel.
15. Filterrelevante Werte liegen als indizierte Spalten vor, nie ausschliesslich in JSONB.

Konsumierbare API-Projektion

Konsumenten (UI, LLM, Export) erhalten nicht das rohe Schema, sondern eine versionierte Projektion. Damit bleibt das interne Schema erweiterbar, ohne Konsumenten zu brechen.

GET /api/v1/results
GET /api/v1/results/{id}
GET /api/v1/results/{id}/evidence
GET /api/v1/results/schema
GET /api/v1/assets
GET /api/v1/edges

Die UI rendert ausschließlich gelieferte Facetten und Resultwerte. Keine toolabhängige Sonderlogik im Client.

Bewusst deferiert

- GPO-/Baseline-Vergleichs-Submodell: Setting-Identitaet pro Namespace, Verdikt-Logik,
  "nicht konfiguriert" vs. "abwesend", Baseline-Registry. Der result_deviation-Block ist
  strukturell bereits vorhanden.
- CIS-CAT-Adapter: XCCDF/ARF vs. JSON nach Verifikation der Lizenzausgabe.
- Elevationsregeln, die einzelne Compliance-/Policy-Befunde auf is_ioe = true heben.
- Rekonziliationsregeln fuer synthetic-Knoten.
- Stufe-B-Kanten (Session, AdminTo, DACL) aus BloodHound-/SharpHound-Klasse-Quellen.
- Mandanten-/Isolationsmodell ueber project_id hinaus.
- Physisches DDL: aus diesem Modell mechanisch ableitbar (Typen, Indizes, Constraints
  sind bereits annotiert).

Changelog

1.0.0   Erstfassung des konsolidierten Modells. Vereint das kanonische Result-Modell
        (Haupttabelle, Kindtabellen, Provenance, Lifecycle) mit RESULT_CONTRACT 1.0.0
        (result_type-Achsen, is_ioe als abgeleitete Facette, typspezifische Bloecke)
        und der Asset-/Edge-Graphschicht. Filteroberfläche als indizierte Spaltenmenge
        definiert. Implementierungsreif bis auf das deferierte GPO-Submodell.