import { buildAuditPayload, createSqlClient, getActiveTemplateVersions, loadTemplateSourceByOrganizationName, loadTemplateSourceByRelativePath, summarizeIssues, writeAuditArtifact, } from './pdf-audit-utils.ts'; type IntegrityIssue = { kind: | 'DUPLICATE_ACTIVE_TEMPLATE' | 'DUPLICATE_ACTIVE_VERSION' | 'ACTIVE_VERSION_WITHOUT_TEMPLATE' | 'MAPPING_WITHOUT_VERSION' | 'INVALID_APPROVED_TEMPLATE_VERSION' | 'ACTIVE_VERSION_SCHEMA_DRIFT' | 'MISSING_RETAINED_INACTIVE_VERSION'; message: string; context: Record; }; async function main() { const sql = createSqlClient(); try { const [ activeTemplateVersions, versionRows, orphanMappingRows, payload, templateRows, ] = await Promise.all([ getActiveTemplateVersions(sql), sql` select v.id, v.template_id as "templateId", v.organization_id as "organizationId", o.name as "organizationName", v.version, v.file_path as "filePath", v.is_active as "isActive", v.schema_json as "schemaJson", t.id as "resolvedTemplateId" from crm_document_template_versions v left join crm_document_templates t on t.id = v.template_id left join organizations o on o.id = v.organization_id where v.deleted_at is null `, sql` select m.id, m.template_version_id as "templateVersionId", m.placeholder_key as "placeholderKey" from crm_document_template_mappings m left join crm_document_template_versions v on v.id = m.template_version_id where m.deleted_at is null and (v.id is null or v.deleted_at is not null) `, buildAuditPayload(sql), sql` select t.id, t.organization_id as "organizationId", o.name as "organizationName", t.template_name as "templateName" from crm_document_templates t inner join organizations o on o.id = t.organization_id where t.deleted_at is null `, ]); const issues: IntegrityIssue[] = []; const templateBuckets = new Map>>(); for (const record of activeTemplateVersions as unknown as Array>) { const bucketKey = `${String(record.organizationId)}:${String(record.templateName)}`; const records = templateBuckets.get(bucketKey) ?? []; records.push(record); templateBuckets.set(bucketKey, records); } for (const [bucketKey, records] of templateBuckets.entries()) { if (records.length <= 1) { continue; } issues.push({ kind: 'DUPLICATE_ACTIVE_TEMPLATE', message: `Multiple active templates found for ${bucketKey}.`, context: { bucketKey, templateIds: records.map((record) => record.templateId), templates: records.map((record) => ({ templateId: record.templateId, templateName: record.templateName, versionId: record.versionId, version: record.version, })), }, }); } const versionBuckets = new Map>>(); for (const row of versionRows as Array>) { if (!row.isActive) { continue; } const items = versionBuckets.get(String(row.templateId)) ?? []; items.push(row); versionBuckets.set(String(row.templateId), items); if (!row.resolvedTemplateId) { issues.push({ kind: 'ACTIVE_VERSION_WITHOUT_TEMPLATE', message: `Active version ${String(row.id)} has no parent template.`, context: row, }); } } for (const [templateId, rows] of versionBuckets.entries()) { if (rows.length <= 1) { continue; } issues.push({ kind: 'DUPLICATE_ACTIVE_VERSION', message: `Template ${templateId} has more than one active version.`, context: { templateId, versions: rows.map((row) => ({ id: row.id, version: row.version, })), }, }); } for (const templateRow of templateRows as Array>) { const templateId = String(templateRow.id); const rows = (versionRows as Array>).filter( (row) => String(row.templateId) === templateId, ); const activeRows = rows.filter((row) => Boolean(row.isActive)); const inactiveRows = rows.filter((row) => !row.isActive); const templateSource = loadTemplateSourceByRelativePath( (activeRows[0]?.filePath as string | null | undefined) ?? null, ) ?? loadTemplateSourceByOrganizationName(String(templateRow.organizationName)); if (templateSource && activeRows.length === 1) { const activeSchema = typeof activeRows[0].schemaJson === 'string' ? JSON.parse(String(activeRows[0].schemaJson)) : activeRows[0].schemaJson; if (JSON.stringify(activeSchema) !== JSON.stringify(templateSource.schemaJson)) { issues.push({ kind: 'ACTIVE_VERSION_SCHEMA_DRIFT', message: `Active template version does not match source JSON for ${String(templateRow.templateName)}.`, context: { templateId, organizationName: templateRow.organizationName, activeVersionId: activeRows[0].id, activeVersion: activeRows[0].version, sourceFile: templateSource.relativePath, }, }); } } if (rows.length > 1 && inactiveRows.length === 0) { issues.push({ kind: 'MISSING_RETAINED_INACTIVE_VERSION', message: `Template ${String(templateRow.templateName)} has no retained inactive historical version.`, context: { templateId, organizationName: templateRow.organizationName, }, }); } } for (const row of orphanMappingRows as Array>) { issues.push({ kind: 'MAPPING_WITHOUT_VERSION', message: `Mapping ${String(row.id)} points to a missing template version.`, context: row, }); } const approvedTemplateVersionExists = payload.approvedTemplateVersionId ? (versionRows as Array>).some( (row) => String(row.id) === payload.approvedTemplateVersionId, ) : true; if (payload.approvedTemplateVersionId && !approvedTemplateVersionExists) { issues.push({ kind: 'INVALID_APPROVED_TEMPLATE_VERSION', message: 'Approved snapshot references a template version that no longer exists.', context: { quotationCode: payload.quotationCode, approvedTemplateVersionId: payload.approvedTemplateVersionId, }, }); } const overallStatus = summarizeIssues( issues.length === 0 ? ['PASS'] : issues.map(() => 'FAIL'), ); const artifactPath = await writeAuditArtifact('template-version-report.json', { audit: 'template-version-integrity', overallStatus, activeTemplateCount: activeTemplateVersions.length, quotationCode: payload.quotationCode, approvedTemplateVersionId: payload.approvedTemplateVersionId, approvedTemplateVersionIsActive: payload.approvedTemplateVersionId ? (versionRows as Array>).some( (row) => String(row.id) === payload.approvedTemplateVersionId && Boolean(row.isActive), ) : false, issues, }); console.log( JSON.stringify( { audit: 'template-version-integrity', overallStatus, activeTemplateCount: activeTemplateVersions.length, quotationCode: payload.quotationCode, approvedTemplateVersionId: payload.approvedTemplateVersionId, approvedTemplateVersionIsActive: payload.approvedTemplateVersionId ? (versionRows as Array>).some( (row) => String(row.id) === payload.approvedTemplateVersionId && Boolean(row.isActive), ) : false, issues, artifactPath, }, null, 2, ), ); } finally { await sql.end({ timeout: 5 }); } } main().catch((error) => { console.error(`[audit-template-version-integrity] ${error.message}`); process.exit(1); });