// Explicit, reviewed identity repair. Default is read-only; no department-name mapping. import { PrismaClient } from '@prisma/client' import { mkdir, writeFile } from 'node:fs/promises' import path from 'node:path' import { parseArgs } from 'node:util' const { values } = parseArgs({ options: { 'association-id': { type: 'string' }, 'organization-unit-id': { type: 'string' }, 'expected-pack-id': { type: 'string' }, 'expected-old-path': { type: 'string' }, 'backup-dir': { type: 'string' }, apply: { type: 'boolean', default: false }, } }) for (const key of ['association-id', 'organization-unit-id', 'expected-pack-id', 'expected-old-path']) { if (!values[key]) throw new Error(`Missing --${key}`) } if (values.apply && !values['backup-dir']) throw new Error('--apply requires --backup-dir') const db = new PrismaClient() try { const row = await db.materialPermissionPackDepartment.findUnique({ where: { id: values['association-id'] } }) const unit = await db.organizationUnit.findUnique({ where: { id: values['organization-unit-id'] } }) if (!row || !unit?.isActive || row.packId !== values['expected-pack-id'] || row.departmentPath !== values['expected-old-path']) throw new Error('Reviewed association or current active department differs; stopped') if (row.organizationUnitId && row.organizationUnitId !== unit.id) throw new Error('Association already bound to another identity; stopped') const conflict = await db.materialPermissionPackDepartment.findUnique({ where: { organizationUnitId: unit.id } }) if (conflict && conflict.id !== row.id) throw new Error('Department identity already assigned; stopped') const plan = { before: row, after: { organizationUnitId: unit.id, departmentPath: unit.path }, sourceDeptId: unit.sourceDeptId } console.log(JSON.stringify({ mode: values.apply ? 'apply' : 'dry-run', ...plan }, null, 2)) if (values.apply) { const dir = path.resolve(values['backup-dir']) await mkdir(dir, { recursive: true, mode: 0o700 }) await writeFile(path.join(dir, `permission-department-${row.id}-${Date.now()}.json`), JSON.stringify(plan, null, 2), { mode: 0o600, flag: 'wx' }) await db.$transaction(async (tx) => { const currentUnit = await tx.organizationUnit.findUnique({ where: { id: unit.id } }) if (!currentUnit?.isActive || currentUnit.path !== unit.path) throw new Error('Department changed during repair; stopped') const result = await tx.materialPermissionPackDepartment.updateMany({ where: { id: row.id, packId: row.packId, departmentPath: row.departmentPath, organizationUnitId: row.organizationUnitId }, data: plan.after, }) if (result.count !== 1) throw new Error('Association changed during repair; stopped') }, { isolationLevel: 'Serializable' }) console.log('Verified identity repair applied; backup retained') } } finally { await db.$disconnect() }