import fs from 'node:fs' import path from 'node:path' import { spawnSync } from 'node:child_process' const gen=spawnSync(process.execPath,['scripts/generate-telemetry-contracts.mjs'],{stdio:'inherit'});if(gen.status!==0)process.exit(gen.status??1) const manifest=JSON.parse(fs.readFileSync('generated/telemetry-manifest.json','utf8')) const source=JSON.parse(fs.readFileSync('contracts/telemetry/kbx.telemetry.json','utf8')) const errors=[] if(manifest.events.length<20)errors.push('telemetry catalog unexpectedly small') if(!manifest.metrics.some(x=>x.key==='manual_intervention_rate'))errors.push('Manual Intervention Rate metric missing') if(source.principles.noSensitiveData!==true||source.principles.noRawBusinessData!==true)errors.push('privacy principles must prohibit sensitive/raw business data') const forbidden=/(phone|address|name|orderNo|customer|itemCode|barcode|keyword|query|email|entityId)/i for(const e of manifest.events)for(const a of e.allowedAttributes??[])if(forbidden.test(a))errors.push(`forbidden telemetry attribute: ${e.name}.${a}`) for(const e of manifest.events.filter(x=>x.requiresDuration))if(!['task.complete','task.abandon','command.succeeded','command.failed','excel.import.completed','excel.import.failed','exception.resolved'].includes(e.name))errors.push(`review duration event: ${e.name}`) const migration=fs.readFileSync('backend/Database/Migrations/20260808_011_kbx_ux_telemetry.sql','utf8') for(const table of ['kbx.ux_events','kbx.ux_business_outcomes'])if(!migration.includes(table))errors.push(`migration missing ${table}`) const appFiles=[] function walk(d){for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name);e.isDirectory()?walk(p):appFiles.push(p)}}walk('apps/web/src') for(const file of appFiles.filter(x=>/\.(ts|vue)$/.test(x))){const text=fs.readFileSync(file,'utf8');if(/kbxTelemetry\.track\([^'\"]/.test(text))errors.push(`${file}: dynamic telemetry event names are forbidden`)} const ts=fs.readFileSync('packages/kbx-contracts/src/generated/telemetryCatalog.ts','utf8'),cs=fs.readFileSync('backend/Shared/Telemetry/Generated/KbxTelemetryCatalog.g.cs','utf8') const tsha=ts.match(/kbxTelemetrySourceSha256 = '([a-f0-9]+)'/)?.[1], cssha=cs.match(/SourceSha256 = "([a-f0-9]+)"/)?.[1] if(!tsha||tsha!==cssha)errors.push('TS/C# telemetry source SHA mismatch') if(!manifest.events.some(x=>x.name==='experiment.exposed'))errors.push('experiment exposure event missing') if(!fs.existsSync('apps/web/src/modules/common/ux-metrics/UxMetricsPage.vue'))errors.push('COMMON-UX-001 reference screen missing') if(errors.length){console.error('KBX telemetry governance FAILED');errors.forEach(x=>console.error(' - '+x));process.exit(1)} console.log(`KBX telemetry governance PASS (${manifest.events.length} events, ${manifest.metrics.length} metrics)`)