59 lines
2.5 KiB
TypeScript
59 lines
2.5 KiB
TypeScript
import { discloseResultDbProjectionWithTrace, RESULT_DB_DISCLOSURE_POLICY_VERSION } from './result-db-disclosure';
|
|
import { RESULT_DB_IDENTITY_GUARD_VERSION, type ResultDbIdentityGuard } from './result-db-identity-guard';
|
|
import type { UnifiedResultProjection } from './unified-result';
|
|
|
|
/**
|
|
* Materialized Result DB (user decision 2026-09-28): rows are disclosed, checked and stored when the
|
|
* source data changes. UI, API and JSONL export read only these stored rows, so what is shown is
|
|
* exactly what is delivered; each row carries its content-free redaction trace.
|
|
*/
|
|
export interface ResultDbMaterializationStatus {
|
|
/** `current`: stored rows match the current repository revision and policy versions. */
|
|
state: 'current' | 'outdated' | 'not-built';
|
|
sourceRevision?: number;
|
|
repositoryRevision: number;
|
|
disclosurePolicyVersion?: string;
|
|
identityGuardVersion?: string;
|
|
materializedAt?: string;
|
|
rowCount: number;
|
|
redactionCount: number;
|
|
/** Rows in which the identity guard withheld at least one complete field. */
|
|
guardWithheldRowCount: number;
|
|
}
|
|
|
|
export interface MaterializedResultDb {
|
|
rows: UnifiedResultProjection[];
|
|
status: ResultDbMaterializationStatus;
|
|
}
|
|
|
|
export function materializeResultDb(
|
|
sourceResults: UnifiedResultProjection[],
|
|
guards: Map<string, ResultDbIdentityGuard>,
|
|
sourceRevision: number,
|
|
materializedAt: string
|
|
): MaterializedResultDb {
|
|
const rows = sourceResults.map(item => discloseResultDbProjectionWithTrace(item, guards.get(item.projectId)));
|
|
return {
|
|
rows,
|
|
status: {
|
|
state: 'current',
|
|
sourceRevision,
|
|
repositoryRevision: sourceRevision,
|
|
disclosurePolicyVersion: RESULT_DB_DISCLOSURE_POLICY_VERSION,
|
|
identityGuardVersion: RESULT_DB_IDENTITY_GUARD_VERSION,
|
|
materializedAt,
|
|
rowCount: rows.length,
|
|
redactionCount: rows.reduce((sum, row) => sum
|
|
+ (row.disclosureTrace?.redactions.reduce((total, entry) => total + entry.count, 0) ?? 0), 0),
|
|
guardWithheldRowCount: rows.filter(row => row.disclosureTrace?.guardWithheldFields.length).length
|
|
}
|
|
};
|
|
}
|
|
|
|
/** A stored state is current only for the same repository revision and the same policy versions. */
|
|
export function isResultDbCurrent(status: Pick<ResultDbMaterializationStatus, 'sourceRevision' | 'disclosurePolicyVersion' | 'identityGuardVersion'> | undefined, repositoryRevision: number): boolean {
|
|
return status?.sourceRevision === repositoryRevision
|
|
&& status.disclosurePolicyVersion === RESULT_DB_DISCLOSURE_POLICY_VERSION
|
|
&& status.identityGuardVersion === RESULT_DB_IDENTITY_GUARD_VERSION;
|
|
}
|