import type { DbStructureTable } from '../../domain/db-structure'; /** * Presentation-only process view of the relational schema (user request 2026-09-25): * which tables each processing step reads and writes, as a program flow chart. * Assignments name exact tables; a table that is not listed appears under * "Other tables" so nothing is hidden when the schema grows. */ export type DbFlowLane = 'shared' | 'scanner' | 'gpo' | 'reference'; export interface DbFlowStepDefinition { id: string; lane: DbFlowLane; title: string; purpose: string; tables: string[]; } export const DB_FLOW_STEPS: DbFlowStepDefinition[] = [ { id: 'foundation', lane: 'shared', title: 'Projects & assessments', purpose: 'Client project, timeslots and the repository revision every step belongs to.', tables: ['projects', 'assessments', 'repository_state'] }, { id: 'upload', lane: 'shared', title: 'Upload & custody', purpose: 'Original files, their fingerprints and processing jobs; scanner reports and GPO backups enter here.', tables: ['import_files', 'import_artifacts', 'import_fingerprints', 'ingestion_jobs', 'artifact_deletion_outbox'] }, { id: 'scanner-processing', lane: 'scanner', title: 'Report processing', purpose: 'One scan run per imported report: extracted tables, report sections and diagnostics.', tables: ['scan_runs', 'import_processing_runs', 'source_tables', 'scanner_report_sections', 'import_diagnostics'] }, { id: 'scanner-mapping', lane: 'scanner', title: 'Recognition & mapping', purpose: 'Scanner profiles and comparison rules that turn source rows into canonical roles.', tables: ['profile_registrations', 'local_comparison_rules'] }, { id: 'scanner-canonical', lane: 'scanner', title: 'Canonical model', purpose: 'Tool-independent assets, their properties and relations, and environment facts.', tables: ['canonical_assets', 'asset_properties', 'asset_edges', 'edge_coverage', 'environment_observations'] }, { id: 'scanner-findings', lane: 'scanner', title: 'Findings & evidence', purpose: 'Rule definitions, occurrences per run, their categories and linked evidence.', tables: ['finding_definitions', 'finding_occurrences', 'finding_occurrence_categories', 'result_categories', 'evidence'] }, { id: 'gpo-inventory', lane: 'gpo', title: 'Backup inventory', purpose: 'One record per GPO inside an uploaded backup, with its parsed settings.', tables: ['gpo_backup_instances'] }, { id: 'gpo-identity', lane: 'gpo', title: 'Policy identity', purpose: 'Resolves every setting to a technical policy identity.', tables: ['policy_identity_analysis_runs', 'policy_identity_resolutions', 'policy_identities'] }, { id: 'gpo-aggregation', lane: 'gpo', title: 'Snapshot aggregation', purpose: 'Combines all GPOs of a backup set into one state per setting.', tables: ['policy_snapshot_aggregation_runs', 'policy_snapshot_aggregation_sources', 'policy_snapshot_states', 'environment_profiles'] }, { id: 'gpo-evaluation', lane: 'gpo', title: 'Baseline evaluation', purpose: 'Compares each setting with the Microsoft baseline variants.', tables: ['policy_evaluation_runs', 'policy_evaluation_results', 'policy_evaluation_evidence', 'policy_evaluation_baseline_sources'] }, { id: 'gpo-findings', lane: 'gpo', title: 'Policy findings', purpose: 'Deviations published as findings for Work.', tables: ['policy_finding_runs', 'policy_finding_occurrences'] }, { id: 'reference-baselines', lane: 'reference', title: 'Microsoft baselines', purpose: 'Reference input: imported baseline packages, their analyses, settings and ADMX catalogs.', tables: ['microsoft_security_baselines', 'microsoft_security_baseline_analyses', 'microsoft_security_baseline_settings', 'microsoft_security_baseline_classifications', 'admx_catalog_versions'] }, { id: 'review', lane: 'shared', title: 'Work & review', purpose: 'Client-owned review state and accepted risks for findings of both paths.', tables: ['reviews'] }, { id: 'comparison', lane: 'shared', title: 'Comparison over time', purpose: 'Timeslot comparisons and the lifecycle of findings between assessments.', tables: ['comparisons', 'timeslot_evidence_comparisons', 'finding_obligations', 'finding_obligation_transitions', 'finding_obligation_scope_decisions'] } ]; export interface DbFlowStep extends DbFlowStepDefinition { resolved: DbStructureTable[]; } const shortName = (name: string) => name.replace(/^assessor_/, ''); /** Resolves the flow against the live schema; unlisted tables are returned separately. */ export function buildDbStructureFlow(tables: DbStructureTable[]): { steps: DbFlowStep[]; other: DbStructureTable[] } { const byShortName = new Map(tables.map(table => [shortName(table.name), table])); const assigned = new Set(); const steps = DB_FLOW_STEPS.map(step => { const resolved = step.tables.flatMap(name => { const table = byShortName.get(name); if (table) assigned.add(table.name); return table ? [table] : []; }); return { ...step, resolved }; }).filter(step => step.resolved.length > 0); return { steps, other: tables.filter(table => !assigned.has(table.name)) }; } /** Tables referenced by, or referencing, the selected table. */ export function dbStructureNeighbours(tables: DbStructureTable[], selected: string): { outgoing: string[]; incoming: string[] } { const table = tables.find(item => item.name === selected); return { outgoing: [...new Set(table?.foreignKeys.map(key => key.referencedTable).filter(name => name !== selected) ?? [])], incoming: [...new Set(tables.filter(item => item.name !== selected && item.foreignKeys.some(key => key.referencedTable === selected)).map(item => item.name))] }; } export const dbStructureShortName = shortName; /** * Read views without tables of their own. They reference tables owned by the steps * above and are drawn where both processing paths meet. */ export interface DbFlowViewDefinition { id: string; title: string; purpose: string; sources: string[]; } export const DB_FLOW_VIEWS: DbFlowViewDefinition[] = [ { id: 'result-db', title: 'Result DB', purpose: 'Read view, no table of its own: combined at query time from scanner findings, environment facts, GPO comparisons, GPO findings and GPO backup consistency.', sources: ['finding_definitions', 'finding_occurrences', 'finding_occurrence_categories', 'result_categories', 'evidence', 'environment_observations', 'policy_evaluation_results', 'policy_snapshot_states', 'policy_finding_occurrences', 'gpo_backup_instances'] } ]; export function buildDbStructureViews(tables: DbStructureTable[]): Array { const byShortName = new Map(tables.map(table => [shortName(table.name), table])); return DB_FLOW_VIEWS.map(view => ({ ...view, resolved: view.sources.flatMap(name => byShortName.get(name) ?? []) })) .filter(view => view.resolved.length > 0); }