Assessor_2.0/src/features/db-structure/db-structure-flow.ts

105 lines
6.8 KiB
TypeScript

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<string>();
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<DbFlowViewDefinition & { resolved: DbStructureTable[] }> {
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);
}