105 lines
6.8 KiB
TypeScript
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);
|
|
}
|