@open-mercato/enterprise 0.6.1-develop.3081.21270ec58a → 0.6.1-develop.3090.06ab462170

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,8 @@ import { randomUUID } from "crypto";
2
2
  import { UniqueConstraintViolationException } from "@mikro-orm/core";
3
3
  import { sql } from "kysely";
4
4
  import { ActionLog } from "@open-mercato/core/modules/audit_logs/data/entities";
5
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
6
+ import { parseDecryptedFieldValue } from "@open-mercato/shared/lib/encryption/tenantDataEncryptionService";
5
7
  import { emitRecordLocksEvent } from "../events.js";
6
8
  import {
7
9
  RecordLock,
@@ -111,6 +113,25 @@ const MISSING_CONFLICT_VALUE = /* @__PURE__ */ Symbol("record_lock_conflict_miss
111
113
  function isRecordValue(value) {
112
114
  return Boolean(value && typeof value === "object" && !Array.isArray(value));
113
115
  }
116
+ function parseDecryptedJsonLike(value) {
117
+ return typeof value === "string" ? parseDecryptedFieldValue(value) : value;
118
+ }
119
+ function readJsonRecordValue(value) {
120
+ const parsed = parseDecryptedJsonLike(value);
121
+ return isRecordValue(parsed) ? parsed : null;
122
+ }
123
+ function readActionLogChangesJson(log) {
124
+ return readJsonRecordValue(log?.changesJson ?? null);
125
+ }
126
+ function normalizeActionLogPayload(log) {
127
+ if (!log) return null;
128
+ log.changesJson = readJsonRecordValue(log.changesJson);
129
+ log.snapshotBefore = parseDecryptedJsonLike(log.snapshotBefore);
130
+ log.snapshotAfter = parseDecryptedJsonLike(log.snapshotAfter);
131
+ log.contextJson = readJsonRecordValue(log.contextJson);
132
+ log.commandPayload = parseDecryptedJsonLike(log.commandPayload);
133
+ return log;
134
+ }
114
135
  function toIsoDate(value) {
115
136
  if (value instanceof Date) {
116
137
  if (Number.isNaN(value.getTime())) return null;
@@ -1064,7 +1085,13 @@ class RecordLockService {
1064
1085
  if (input.organizationId !== void 0) {
1065
1086
  where.organizationId = normalizeScopeOrganization(input.organizationId);
1066
1087
  }
1067
- return this.em.findOne(ActionLog, where, { orderBy: { createdAt: "desc" } });
1088
+ return normalizeActionLogPayload(await findOneWithDecryption(
1089
+ this.em,
1090
+ ActionLog,
1091
+ where,
1092
+ { orderBy: { createdAt: "desc" } },
1093
+ { tenantId: input.tenantId, organizationId: normalizeScopeOrganization(input.organizationId) }
1094
+ ));
1068
1095
  }
1069
1096
  async findLatestActionLogWithScopeFallback(input) {
1070
1097
  const scoped = await this.findLatestActionLog(input);
@@ -1087,25 +1114,33 @@ class RecordLockService {
1087
1114
  if (input.organizationId !== void 0) {
1088
1115
  where.organizationId = normalizeScopeOrganization(input.organizationId);
1089
1116
  }
1090
- return this.em.findOne(ActionLog, where, { orderBy: { createdAt: "desc" } });
1117
+ return normalizeActionLogPayload(await findOneWithDecryption(
1118
+ this.em,
1119
+ ActionLog,
1120
+ where,
1121
+ { orderBy: { createdAt: "desc" } },
1122
+ { tenantId: input.tenantId, organizationId: normalizeScopeOrganization(input.organizationId) }
1123
+ ));
1091
1124
  }
1092
1125
  summarizeChangedFieldsFromActionLog(log) {
1093
1126
  if (!log) return "";
1094
- if (isRecordValue(log.changesJson)) {
1095
- const fromChanges = Object.keys(log.changesJson).filter((field) => !shouldSkipConflictField(field)).slice(0, 12).map(formatChangedFieldLabel).join(", ");
1127
+ const changesJson = readActionLogChangesJson(log);
1128
+ if (changesJson) {
1129
+ const fromChanges = Object.keys(changesJson).filter((field) => !shouldSkipConflictField(field)).slice(0, 12).map(formatChangedFieldLabel).join(", ");
1096
1130
  if (fromChanges) return fromChanges;
1097
1131
  }
1098
- const before = isRecordValue(log.snapshotBefore) ? log.snapshotBefore : null;
1099
- const after = isRecordValue(log.snapshotAfter) ? log.snapshotAfter : null;
1132
+ const before = readJsonRecordValue(log.snapshotBefore);
1133
+ const after = readJsonRecordValue(log.snapshotAfter);
1100
1134
  if (!before || !after) return "";
1101
1135
  const diffPaths = /* @__PURE__ */ new Set();
1102
1136
  this.collectSnapshotDiffPaths(before, after, null, diffPaths, /* @__PURE__ */ new Set());
1103
1137
  return Array.from(diffPaths).filter((field) => !shouldSkipConflictField(field)).sort((left, right) => left.localeCompare(right)).slice(0, 12).map(formatChangedFieldLabel).join(", ");
1104
1138
  }
1105
1139
  buildIncomingChangeRowsFromActionLog(log) {
1106
- if (!log || !isRecordValue(log.changesJson)) return [];
1140
+ const changesJson = readActionLogChangesJson(log);
1141
+ if (!changesJson) return [];
1107
1142
  const rows = [];
1108
- for (const [rawField, rawChange] of Object.entries(log.changesJson)) {
1143
+ for (const [rawField, rawChange] of Object.entries(changesJson)) {
1109
1144
  if (rows.length >= 12) break;
1110
1145
  if (shouldSkipConflictField(rawField)) continue;
1111
1146
  const change = isRecordValue(rawChange) ? rawChange : {};
@@ -1275,8 +1310,15 @@ class RecordLockService {
1275
1310
  if (!logId) return null;
1276
1311
  let resolved = this.actionLogService ? await this.actionLogService.findById(logId) : null;
1277
1312
  if (!resolved) {
1278
- resolved = await this.em.findOne(ActionLog, { id: logId, deletedAt: null });
1313
+ resolved = await findOneWithDecryption(
1314
+ this.em,
1315
+ ActionLog,
1316
+ { id: logId, deletedAt: null },
1317
+ void 0,
1318
+ { tenantId: scope.tenantId, organizationId: normalizeScopeOrganization(scope.organizationId) }
1319
+ );
1279
1320
  }
1321
+ resolved = normalizeActionLogPayload(resolved);
1280
1322
  if (!resolved || resolved.deletedAt) return null;
1281
1323
  if (resolved.tenantId !== scope.tenantId) return null;
1282
1324
  if (scope.organizationId !== void 0) {
@@ -1318,12 +1360,12 @@ class RecordLockService {
1318
1360
  };
1319
1361
  const baseLog = await this.findActionLogById(conflict.baseActionLogId, scope);
1320
1362
  const incomingLog = await this.findActionLogById(conflict.incomingActionLogId, scope);
1321
- const baseSnapshot = isRecordValue(baseLog?.snapshotAfter) ? baseLog.snapshotAfter : null;
1322
- const incomingBeforeSnapshot = isRecordValue(incomingLog?.snapshotBefore) ? incomingLog.snapshotBefore : null;
1323
- const incomingAfterSnapshot = isRecordValue(incomingLog?.snapshotAfter) ? incomingLog.snapshotAfter : null;
1363
+ const baseSnapshot = readJsonRecordValue(baseLog?.snapshotAfter ?? null);
1364
+ const incomingBeforeSnapshot = readJsonRecordValue(incomingLog?.snapshotBefore ?? null);
1365
+ const incomingAfterSnapshot = readJsonRecordValue(incomingLog?.snapshotAfter ?? null);
1324
1366
  const fallbackBaseSnapshot = baseSnapshot ?? incomingBeforeSnapshot;
1325
1367
  const changeMap = /* @__PURE__ */ new Map();
1326
- const incomingChanges = isRecordValue(incomingLog?.changesJson) ? incomingLog.changesJson : null;
1368
+ const incomingChanges = readActionLogChangesJson(incomingLog);
1327
1369
  if (incomingChanges) {
1328
1370
  for (const [fieldPathRaw, rawChange] of Object.entries(incomingChanges)) {
1329
1371
  const fieldPath = fieldPathRaw.trim();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/record_locks/lib/recordLockService.ts"],
4
- "sourcesContent": ["import { randomUUID } from 'crypto'\nimport { UniqueConstraintViolationException, type FilterQuery } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\nimport { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { emitRecordLocksEvent } from '../events'\nimport {\n RecordLock,\n RecordLockConflict,\n type RecordLockStatus,\n type RecordLockReleaseReason,\n type RecordLockConflictResolution,\n type RecordLockConflictStatus,\n} from '../data/entities'\nimport {\n recordLockMutationHeaderSchema,\n type RecordLockMutationHeaders,\n type RecordLockReleaseInput as RecordLockReleasePayloadInput,\n type RecordLockSettingsInput,\n} from '../data/validators'\nimport {\n DEFAULT_RECORD_LOCK_SETTINGS,\n RECORD_LOCKS_MODULE_ID,\n RECORD_LOCKS_SETTINGS_NAME,\n isRecordLockingEnabledForResource,\n normalizeRecordLockSettings,\n type RecordLockSettings,\n type RecordLockStrategy,\n} from './config'\n\nconst ACTIVE_LOCK_STATUS: RecordLockStatus = 'active'\nconst ACTIVE_SCOPE_UNIQUE_CONSTRAINTS = new Set([\n 'record_locks_active_scope_org_unique',\n 'record_locks_active_scope_tenant_unique',\n 'record_locks_active_scope_user_org_unique',\n 'record_locks_active_scope_user_tenant_unique',\n])\nconst LOCK_CONTENTION_EVENT_TTL_MS = 15_000\nconst PARTICIPANT_REJOIN_AFTER_SAVE_SUPPRESS_MS = 20_000\nconst LOCK_CONTENTION_EVENT_MAX_ENTRIES = 2_000\nconst lockContentionEventThrottle = new Map<string, number>()\nconst LOCK_CLEANUP_INTERVAL_MS = 5 * 60 * 1000\nconst LOCK_RETENTION_MS = 3 * 24 * 60 * 60 * 1000\nconst RESOLVED_CONFLICT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000\nconst PENDING_CONFLICT_RETENTION_MS = 24 * 60 * 60 * 1000\nconst LOCK_CLEANUP_STATE_TTL_MS = 24 * 60 * 60 * 1000\nconst LOCK_CLEANUP_STATE_MAX_ENTRIES = 2_000\nconst lockCleanupStateByTenant = new Map<string, { lastRunAt: number; inFlight: boolean; lastSeenAt: number }>()\n\nexport type RecordLockScope = {\n tenantId: string\n organizationId?: string | null\n userId: string\n}\n\nexport type RecordLockResource = {\n resourceKind: string\n resourceId: string\n}\n\nexport type RecordLockAcquireInput = RecordLockScope & RecordLockResource & {\n lockedByIp?: string | null\n}\n\nexport type RecordLockHeartbeatInput = RecordLockScope & RecordLockResource & {\n token: string\n}\n\nexport type RecordLockReleaseInput = RecordLockScope & RecordLockResource & {\n token?: string\n reason?: Exclude<RecordLockReleaseReason, 'expired' | 'force'>\n} & Pick<RecordLockReleasePayloadInput, 'conflictId' | 'resolution'>\n\nexport type RecordLockForceReleaseInput = RecordLockScope & RecordLockResource & {\n reason?: string | null\n}\n\nexport type RecordLockMutationValidationInput = RecordLockScope & RecordLockResource & {\n method: 'PUT' | 'DELETE'\n headers: Partial<RecordLockMutationHeaders>\n mutationPayload?: Record<string, unknown> | null\n}\n\nexport type RecordLockConflictChange = {\n field: string\n displayValue: unknown\n baseValue: unknown\n incomingValue: unknown\n mineValue: unknown\n}\n\nexport type RecordLockConflictPayload = {\n id: string\n resourceKind: string\n resourceId: string\n baseActionLogId: string | null\n incomingActionLogId: string | null\n allowIncomingOverride: boolean\n canOverrideIncoming: boolean\n resolutionOptions: Array<'accept_mine'>\n changes: RecordLockConflictChange[]\n}\n\nexport type RecordLockView = {\n id: string\n resourceKind: string\n resourceId: string\n token: string | null\n strategy: RecordLockStrategy\n status: RecordLockStatus\n lockedByUserId: string\n lockedByIp: string | null\n baseActionLogId: string | null\n lockedAt: string\n lastHeartbeatAt: string\n expiresAt: string\n participants: RecordLockParticipantView[]\n activeParticipantCount: number\n}\n\nexport type RecordLockParticipantView = {\n userId: string\n lockedByIp: string | null\n lockedAt: string\n lastHeartbeatAt: string\n expiresAt: string\n}\n\nexport type RecordLockAcquireResult = {\n ok: true\n enabled: boolean\n resourceEnabled: boolean\n strategy: RecordLockStrategy\n allowForceUnlock: boolean\n heartbeatSeconds: number\n acquired: boolean\n latestActionLogId: string | null\n lock: RecordLockView | null\n}\n\nexport type RecordLockAcquireFailure = RecordLockValidationFailure & {\n allowForceUnlock: boolean\n}\n\nexport type RecordLockHeartbeatResult = {\n ok: true\n expiresAt: string | null\n}\n\nexport type RecordLockReleaseResult = {\n ok: true\n released: boolean\n conflictResolved: boolean\n}\n\nexport type RecordLockForceReleaseResult = {\n ok: true\n released: boolean\n lock: RecordLockView | null\n}\n\nexport type RecordLockValidationSuccess = {\n ok: true\n enabled: boolean\n resourceEnabled: boolean\n strategy: RecordLockStrategy\n shouldReleaseOnSuccess: boolean\n lock: RecordLockView | null\n latestActionLogId: string | null\n}\n\nexport type RecordLockValidationFailure = {\n ok: false\n status: 409 | 423\n error: string\n code: 'record_lock_conflict' | 'record_locked'\n lock: RecordLockView | null\n conflict?: RecordLockConflictPayload\n}\n\nexport type RecordLockValidationResult = RecordLockValidationSuccess | RecordLockValidationFailure\n\nexport type RecordLockServiceDeps = {\n em: EntityManager\n moduleConfigService?: ModuleConfigService | null\n actionLogService?: ActionLogService | null\n rbacService?: RbacService | null\n}\n\nexport type ParsedRecordLockHeaders = Partial<RecordLockMutationHeaders>\n\nfunction normalizeDate(value: Date): string {\n return value.toISOString()\n}\n\nfunction trimToNull(value: string | null | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length ? trimmed : null\n}\n\nfunction normalizeScopeOrganization(value: string | null | undefined): string | null {\n const trimmed = trimToNull(value)\n return trimmed ?? null\n}\n\nfunction shouldEmitLockContentionEvent(input: {\n tenantId: string\n organizationId?: string | null\n resourceKind: string\n resourceId: string\n lockedByUserId: string\n attemptedByUserId: string\n}): boolean {\n if (process.env.NODE_ENV === 'test') return true\n const now = Date.now()\n const key = [\n input.tenantId,\n normalizeScopeOrganization(input.organizationId) ?? 'global',\n input.resourceKind,\n input.resourceId,\n input.lockedByUserId,\n input.attemptedByUserId,\n ].join('|')\n\n const lastEmittedAt = lockContentionEventThrottle.get(key)\n if (typeof lastEmittedAt === 'number' && now - lastEmittedAt < LOCK_CONTENTION_EVENT_TTL_MS) {\n return false\n }\n\n lockContentionEventThrottle.set(key, now)\n\n for (const [cachedKey, cachedAt] of lockContentionEventThrottle.entries()) {\n if (now - cachedAt > LOCK_CONTENTION_EVENT_TTL_MS) {\n lockContentionEventThrottle.delete(cachedKey)\n }\n }\n if (lockContentionEventThrottle.size > LOCK_CONTENTION_EVENT_MAX_ENTRIES) {\n const oldest = Array.from(lockContentionEventThrottle.entries())\n .sort((left, right) => left[1] - right[1])\n .slice(0, lockContentionEventThrottle.size - LOCK_CONTENTION_EVENT_MAX_ENTRIES)\n for (const [staleKey] of oldest) lockContentionEventThrottle.delete(staleKey)\n }\n\n return true\n}\n\nfunction isActiveLockScopeUniqueViolation(error: unknown): boolean {\n if (error instanceof UniqueConstraintViolationException) {\n const errorWithConstraint = error as unknown as { constraint?: unknown }\n const constraint = typeof errorWithConstraint.constraint === 'string'\n ? errorWithConstraint.constraint\n : null\n if (constraint && ACTIVE_SCOPE_UNIQUE_CONSTRAINTS.has(constraint)) return true\n }\n if (!error || typeof error !== 'object') return false\n const code = (error as { code?: unknown }).code\n if (code !== '23505') return false\n const message = typeof (error as { message?: unknown }).message === 'string'\n ? (error as { message: string }).message.toLowerCase()\n : ''\n for (const constraint of ACTIVE_SCOPE_UNIQUE_CONSTRAINTS) {\n if (message.includes(constraint)) return true\n }\n return false\n}\n\nfunction getKysely(em: EntityManager): Kysely<any> {\n return (em as unknown as { getKysely: () => Kysely<any> }).getKysely()\n}\n\nconst SKIPPED_CONFLICT_FIELDS = new Set([\n 'updatedAt',\n 'updated_at',\n 'createdAt',\n 'created_at',\n 'deletedAt',\n 'deleted_at',\n])\n\nfunction shouldSkipConflictField(path: string): boolean {\n if (!path.trim().length) return true\n if (SKIPPED_CONFLICT_FIELDS.has(path)) return true\n const segments = path.split('.').filter((segment) => segment.length > 0)\n if (!segments.length) return true\n return SKIPPED_CONFLICT_FIELDS.has(segments[segments.length - 1] ?? '')\n}\n\nconst MISSING_CONFLICT_VALUE = Symbol('record_lock_conflict_missing_value')\n\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n return Boolean(value && typeof value === 'object' && !Array.isArray(value))\n}\n\nfunction toIsoDate(value: unknown): string | null {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return null\n return value.toISOString()\n }\n if (typeof value === 'string') {\n const parsed = new Date(value)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction valuesEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean {\n if (Object.is(a, b)) return true\n\n if (a instanceof Date || b instanceof Date) {\n const left = toIsoDate(a)\n const right = toIsoDate(b)\n return left !== null && right !== null && left === right\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let index = 0; index < a.length; index += 1) {\n if (!valuesEqual(a[index], b[index], seen)) return false\n }\n return true\n }\n\n if (isRecordValue(a) && isRecordValue(b)) {\n if (!seen) seen = new Set()\n if (seen.has(a) || seen.has(b)) return false\n seen.add(a)\n seen.add(b)\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false\n if (!valuesEqual(a[key], b[key], seen)) return false\n }\n return true\n }\n\n return false\n}\n\nfunction readPathValue(source: unknown, path: string): unknown | typeof MISSING_CONFLICT_VALUE {\n if (!path.trim().length || !isRecordValue(source)) return MISSING_CONFLICT_VALUE\n if (Object.prototype.hasOwnProperty.call(source, path)) return source[path]\n\n const segments = path.split('.').filter((segment) => segment.length > 0)\n if (!segments.length) return MISSING_CONFLICT_VALUE\n\n let current: unknown = source\n for (const segment of segments) {\n if (!isRecordValue(current)) return MISSING_CONFLICT_VALUE\n if (!Object.prototype.hasOwnProperty.call(current, segment)) return MISSING_CONFLICT_VALUE\n current = current[segment]\n }\n return current\n}\n\nfunction buildPathVariants(path: string): string[] {\n const trimmed = path.trim()\n if (!trimmed.length) return []\n\n const segments = trimmed.split('.').filter((segment) => segment.length > 0)\n if (segments.length <= 1) return [trimmed]\n\n const variants = new Set<string>([trimmed])\n for (let index = 1; index < segments.length; index += 1) {\n variants.add(segments.slice(index).join('.'))\n }\n return Array.from(variants)\n}\n\nfunction readPathValueLoose(source: unknown, path: string): unknown | typeof MISSING_CONFLICT_VALUE {\n const variants = buildPathVariants(path)\n for (const variant of variants) {\n const value = readPathValue(source, variant)\n if (value !== MISSING_CONFLICT_VALUE) return value\n }\n return MISSING_CONFLICT_VALUE\n}\n\nfunction normalizeConflictValue(value: unknown): unknown {\n return value === undefined ? null : value\n}\n\nfunction formatNotificationValue(value: unknown): string {\n if (value === null || value === undefined) return '-'\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (value instanceof Date) return value.toISOString()\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n\nfunction formatChangedFieldLabel(rawField: string): string {\n const trimmedField = rawField.trim()\n const withoutNamespace = trimmedField.includes('::') ? (trimmedField.split('::').pop() ?? trimmedField) : trimmedField\n const withoutPrefix = withoutNamespace.includes('.') ? (withoutNamespace.split('.').pop() ?? withoutNamespace) : withoutNamespace\n const words = withoutPrefix\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[._-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .split(' ')\n .filter(Boolean)\n\n if (!words.length) return trimmedField\n return words\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\nexport function readRecordLockHeaders(headers: Headers): ParsedRecordLockHeaders {\n const raw = {\n resourceKind: trimToNull(headers.get('x-om-record-lock-kind')) ?? undefined,\n resourceId: trimToNull(headers.get('x-om-record-lock-resource-id')) ?? undefined,\n token: trimToNull(headers.get('x-om-record-lock-token')) ?? undefined,\n baseLogId: trimToNull(headers.get('x-om-record-lock-base-log-id')) ?? undefined,\n resolution: trimToNull(headers.get('x-om-record-lock-resolution')) ?? undefined,\n conflictId: trimToNull(headers.get('x-om-record-lock-conflict-id')) ?? undefined,\n }\n\n const parsed = recordLockMutationHeaderSchema.partial().safeParse(raw)\n if (!parsed.success) return {}\n return parsed.data\n}\n\nexport class RecordLockService {\n private readonly em: EntityManager\n\n private readonly moduleConfigService: ModuleConfigService | null\n\n private readonly actionLogService: ActionLogService | null\n\n private readonly rbacService: RbacService | null\n\n constructor(deps: RecordLockServiceDeps) {\n this.em = deps.em\n this.moduleConfigService = deps.moduleConfigService ?? null\n this.actionLogService = deps.actionLogService ?? null\n this.rbacService = deps.rbacService ?? null\n }\n\n async getSettings(): Promise<RecordLockSettings> {\n if (!this.moduleConfigService) return DEFAULT_RECORD_LOCK_SETTINGS\n\n const value = await this.moduleConfigService.getValue<RecordLockSettings>(\n RECORD_LOCKS_MODULE_ID,\n RECORD_LOCKS_SETTINGS_NAME,\n { defaultValue: DEFAULT_RECORD_LOCK_SETTINGS },\n )\n\n return normalizeRecordLockSettings(value ?? DEFAULT_RECORD_LOCK_SETTINGS)\n }\n\n async saveSettings(input: RecordLockSettingsInput): Promise<RecordLockSettings> {\n const settings = normalizeRecordLockSettings(input)\n if (!this.moduleConfigService) return settings\n\n await this.moduleConfigService.setValue(RECORD_LOCKS_MODULE_ID, RECORD_LOCKS_SETTINGS_NAME, settings)\n return settings // NOSONAR \u2014 both paths return settings by design; the branch controls persistence\n }\n\n async acquire(input: RecordLockAcquireInput): Promise<RecordLockAcquireResult | RecordLockAcquireFailure> {\n this.scheduleCleanup(input.tenantId)\n const settings = await this.getSettings()\n const latest = await this.findLatestActionLogWithScopeFallback(input)\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n\n if (!resourceEnabled) {\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: false,\n strategy: settings.strategy,\n allowForceUnlock: settings.allowForceUnlock,\n heartbeatSeconds: settings.heartbeatSeconds,\n acquired: false,\n latestActionLogId: latest?.id ?? null,\n lock: null,\n }\n }\n\n const now = new Date()\n let activeLocks = await this.findActiveLocks(input, now)\n const ownedActiveLock = activeLocks.find((lock) => lock.lockedByUserId === input.userId) ?? null\n const competingActiveLock = activeLocks.find((lock) => lock.lockedByUserId !== input.userId) ?? null\n\n if (settings.strategy === 'pessimistic' && !ownedActiveLock && competingActiveLock) {\n const lockView = this.toLockView(competingActiveLock, false, activeLocks)\n if (shouldEmitLockContentionEvent({\n tenantId: competingActiveLock.tenantId,\n organizationId: competingActiveLock.organizationId,\n resourceKind: competingActiveLock.resourceKind,\n resourceId: competingActiveLock.resourceId,\n lockedByUserId: competingActiveLock.lockedByUserId,\n attemptedByUserId: input.userId,\n })) {\n await emitRecordLocksEvent('record_locks.lock.contended', {\n lockId: competingActiveLock.id,\n resourceKind: competingActiveLock.resourceKind,\n resourceId: competingActiveLock.resourceId,\n tenantId: competingActiveLock.tenantId,\n organizationId: competingActiveLock.organizationId,\n lockedByUserId: competingActiveLock.lockedByUserId,\n attemptedByUserId: input.userId,\n })\n }\n return {\n ok: false,\n status: 423,\n error: 'Record is currently locked by another user',\n code: 'record_locked',\n allowForceUnlock: settings.allowForceUnlock,\n lock: lockView,\n }\n }\n\n if (ownedActiveLock) {\n ownedActiveLock.strategy = settings.strategy\n ownedActiveLock.lockedByIp = input.lockedByIp ?? ownedActiveLock.lockedByIp ?? null\n ownedActiveLock.lastHeartbeatAt = now\n ownedActiveLock.expiresAt = new Date(now.getTime() + settings.timeoutSeconds * 1000)\n await this.em.flush()\n\n activeLocks = await this.findActiveLocks(input, now)\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n allowForceUnlock: settings.allowForceUnlock,\n heartbeatSeconds: settings.heartbeatSeconds,\n acquired: false,\n latestActionLogId: latest?.id ?? null,\n lock: this.toLockView(ownedActiveLock, true, activeLocks),\n }\n }\n\n const lock = this.em.create(RecordLock, {\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n token: randomUUID(),\n strategy: settings.strategy,\n status: ACTIVE_LOCK_STATUS,\n lockedByUserId: input.userId,\n lockedByIp: input.lockedByIp ?? null,\n baseActionLogId: latest?.id ?? null,\n lockedAt: now,\n lastHeartbeatAt: now,\n expiresAt: new Date(now.getTime() + settings.timeoutSeconds * 1000),\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n })\n\n this.em.persist(lock)\n let createdNewLock = true\n try {\n await this.em.flush()\n } catch (error) {\n if (!isActiveLockScopeUniqueViolation(error)) throw error\n const clear = (this.em as { clear?: () => void }).clear\n if (typeof clear === 'function') clear.call(this.em)\n const locksAfterCollision = await this.findActiveLocks(input, now)\n const competingAfterCollision = locksAfterCollision.find((item) => item.lockedByUserId !== input.userId) ?? null\n if (settings.strategy === 'pessimistic' && competingAfterCollision) {\n if (shouldEmitLockContentionEvent({\n tenantId: competingAfterCollision.tenantId,\n organizationId: competingAfterCollision.organizationId,\n resourceKind: competingAfterCollision.resourceKind,\n resourceId: competingAfterCollision.resourceId,\n lockedByUserId: competingAfterCollision.lockedByUserId,\n attemptedByUserId: input.userId,\n })) {\n await emitRecordLocksEvent('record_locks.lock.contended', {\n lockId: competingAfterCollision.id,\n resourceKind: competingAfterCollision.resourceKind,\n resourceId: competingAfterCollision.resourceId,\n tenantId: competingAfterCollision.tenantId,\n organizationId: competingAfterCollision.organizationId,\n lockedByUserId: competingAfterCollision.lockedByUserId,\n attemptedByUserId: input.userId,\n })\n }\n return {\n ok: false,\n status: 423,\n error: 'Record is currently locked by another user',\n code: 'record_locked',\n allowForceUnlock: settings.allowForceUnlock,\n lock: this.toLockView(competingAfterCollision, false, locksAfterCollision),\n }\n }\n const existingOwned = await this.findOwnedActiveLock(input)\n if (!existingOwned) throw error\n existingOwned.strategy = settings.strategy\n existingOwned.lockedByIp = input.lockedByIp ?? existingOwned.lockedByIp ?? null\n existingOwned.lastHeartbeatAt = now\n existingOwned.expiresAt = new Date(now.getTime() + settings.timeoutSeconds * 1000)\n await this.em.flush()\n createdNewLock = false\n }\n\n const activeAfterAcquire = await this.findActiveLocks(input, now)\n const ownedAfterAcquire = activeAfterAcquire.find((item) => item.lockedByUserId === input.userId)\n ?? await this.findOwnedActiveLock(input)\n ?? lock\n ?? null\n if (!ownedAfterAcquire) {\n const fallbackLock = activeAfterAcquire[0] ?? null\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n allowForceUnlock: settings.allowForceUnlock,\n heartbeatSeconds: settings.heartbeatSeconds,\n acquired: false,\n latestActionLogId: latest?.id ?? null,\n lock: fallbackLock ? this.toLockView(fallbackLock, false, activeAfterAcquire) : null,\n }\n }\n\n if (createdNewLock) {\n await emitRecordLocksEvent('record_locks.lock.acquired', {\n lockId: ownedAfterAcquire.id,\n resourceKind: ownedAfterAcquire.resourceKind,\n resourceId: ownedAfterAcquire.resourceId,\n tenantId: ownedAfterAcquire.tenantId,\n organizationId: ownedAfterAcquire.organizationId,\n lockedByUserId: ownedAfterAcquire.lockedByUserId,\n strategy: ownedAfterAcquire.strategy,\n baseActionLogId: ownedAfterAcquire.baseActionLogId,\n activeParticipantCount: activeAfterAcquire.length,\n })\n\n const recipientUserIds = activeAfterAcquire\n .filter((item) => item.lockedByUserId !== input.userId)\n .map((item) => item.lockedByUserId)\n const shouldSuppressJoinNotification = await this.hasRecentSavedRelease({\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n userId: input.userId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n now,\n })\n\n if (!shouldSuppressJoinNotification) {\n await emitRecordLocksEvent('record_locks.participant.joined', {\n lockId: ownedAfterAcquire.id,\n resourceKind: ownedAfterAcquire.resourceKind,\n resourceId: ownedAfterAcquire.resourceId,\n tenantId: ownedAfterAcquire.tenantId,\n organizationId: ownedAfterAcquire.organizationId,\n joinedUserId: input.userId,\n joinedIp: ownedAfterAcquire.lockedByIp ?? null,\n recipientUserIds,\n activeParticipantCount: activeAfterAcquire.length,\n })\n }\n }\n\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n allowForceUnlock: settings.allowForceUnlock,\n heartbeatSeconds: settings.heartbeatSeconds,\n acquired: createdNewLock,\n latestActionLogId: latest?.id ?? null,\n lock: this.toLockView(ownedAfterAcquire, true, activeAfterAcquire),\n }\n }\n\n async heartbeat(input: RecordLockHeartbeatInput): Promise<RecordLockHeartbeatResult> {\n const settings = await this.getSettings()\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n if (!resourceEnabled) return { ok: true, expiresAt: null }\n\n const lock = await this.findOwnedLockByToken(input)\n if (!lock) return { ok: true, expiresAt: null }\n\n const now = new Date()\n if (lock.expiresAt <= now) {\n this.markLockReleased(lock, {\n status: 'expired',\n reason: 'expired',\n releasedByUserId: lock.lockedByUserId,\n now,\n })\n await this.em.flush()\n return { ok: true, expiresAt: null }\n }\n\n lock.lastHeartbeatAt = now\n lock.expiresAt = new Date(now.getTime() + settings.timeoutSeconds * 1000)\n await this.em.flush()\n return { ok: true, expiresAt: normalizeDate(lock.expiresAt) }\n }\n\n async release(input: RecordLockReleaseInput): Promise<RecordLockReleaseResult> {\n const settings = await this.getSettings()\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n if (!resourceEnabled) return { ok: true, released: false, conflictResolved: false }\n\n let conflictResolved = false\n if (input.reason === 'conflict_resolved' && input.conflictId && input.resolution === 'accept_incoming') {\n const conflict = await this.findConflictById(input.conflictId, input)\n if (conflict && conflict.status === 'pending' && conflict.conflictActorUserId === input.userId) {\n await this.resolveConflict(conflict, input.resolution, input.userId)\n conflictResolved = true\n }\n }\n\n const lock = input.token\n ? await this.findOwnedLockByToken(input)\n : await this.findOwnedActiveLock(input)\n if (!lock) return { ok: true, released: false, conflictResolved }\n\n const now = new Date()\n this.markLockReleased(lock, {\n status: 'released',\n reason: input.reason ?? 'cancelled',\n releasedByUserId: input.userId,\n now,\n })\n await this.em.flush()\n\n await emitRecordLocksEvent('record_locks.lock.released', {\n lockId: lock.id,\n resourceKind: lock.resourceKind,\n resourceId: lock.resourceId,\n tenantId: lock.tenantId,\n organizationId: lock.organizationId,\n lockedByUserId: lock.lockedByUserId,\n releasedByUserId: input.userId,\n reason: lock.releaseReason,\n })\n\n if (lock.releaseReason === 'unmount') {\n const remainingActiveLocks = await this.findActiveLocks(input, now)\n const recipientUserIds = remainingActiveLocks\n .map((activeLock) => activeLock.lockedByUserId)\n .filter((userId) => userId !== lock.lockedByUserId)\n\n if (recipientUserIds.length) {\n await emitRecordLocksEvent('record_locks.participant.left', {\n lockId: lock.id,\n resourceKind: lock.resourceKind,\n resourceId: lock.resourceId,\n tenantId: lock.tenantId,\n organizationId: lock.organizationId,\n leftUserId: lock.lockedByUserId,\n reason: 'unmount',\n recipientUserIds,\n activeParticipantCount: remainingActiveLocks.length,\n })\n }\n }\n\n return { ok: true, released: true, conflictResolved }\n }\n\n async forceRelease(input: RecordLockForceReleaseInput): Promise<RecordLockForceReleaseResult> {\n const settings = await this.getSettings()\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n const canForceRelease = await this.canUserForceRelease(input, settings)\n if (!resourceEnabled || !settings.allowForceUnlock || !canForceRelease) {\n return { ok: true, released: false, lock: null }\n }\n\n const now = new Date()\n const activeLocks = this.sortLocksByJoinOrder(await this.findActiveLocks(input, now))\n const lock = activeLocks[0] ?? null\n if (!lock) return { ok: true, released: false, lock: null }\n\n this.markLockReleased(lock, {\n status: 'force_released',\n reason: 'force',\n releasedByUserId: input.userId,\n now,\n })\n await this.em.flush()\n\n await emitRecordLocksEvent('record_locks.lock.force_released', {\n lockId: lock.id,\n resourceKind: lock.resourceKind,\n resourceId: lock.resourceId,\n tenantId: lock.tenantId,\n organizationId: lock.organizationId,\n lockedByUserId: lock.lockedByUserId,\n releasedByUserId: input.userId,\n reason: input.reason ?? null,\n })\n\n const remainingLocks = this.sortLocksByJoinOrder(activeLocks.filter((item) => item.id !== lock.id))\n const nextInQueue = remainingLocks[0] ?? null\n return { ok: true, released: true, lock: nextInQueue ? this.toLockView(nextInQueue, false, remainingLocks) : null }\n }\n\n async validateMutation(input: RecordLockMutationValidationInput): Promise<RecordLockValidationResult> {\n this.scheduleCleanup(input.tenantId)\n const settings = await this.getSettings()\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n const canOverrideIncoming = await this.canUserOverrideIncoming(input, settings)\n\n if (!resourceEnabled) {\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: false,\n strategy: settings.strategy,\n shouldReleaseOnSuccess: false,\n lock: null,\n latestActionLogId: null,\n }\n }\n\n const parsedHeaders = this.normalizeMutationHeaders(input.headers)\n const keepMineResolution = parsedHeaders.resolution === 'accept_mine' || parsedHeaders.resolution === 'merged'\n ? parsedHeaders.resolution\n : null\n const hasKeepMineIntent = keepMineResolution !== null\n const now = new Date()\n const activeLocks = await this.findActiveLocks(input, now)\n const ownedActiveLock = activeLocks.find((lock) => lock.lockedByUserId === input.userId) ?? null\n const competingLock = activeLocks.find((lock) => lock.lockedByUserId !== input.userId) ?? null\n const latest = await this.findLatestActionLogWithScopeFallback(input)\n const shouldReleaseOnSuccess = Boolean(\n ownedActiveLock\n && (!parsedHeaders.token || ownedActiveLock.token === parsedHeaders.token),\n )\n\n if (settings.strategy === 'pessimistic') {\n if (competingLock && !ownedActiveLock) {\n return {\n ok: false,\n status: 423,\n error: 'Record is currently locked by another user',\n code: 'record_locked',\n lock: this.toLockView(competingLock, false, activeLocks),\n }\n }\n\n if (ownedActiveLock) {\n if (parsedHeaders.token && ownedActiveLock.token !== parsedHeaders.token) {\n return {\n ok: false,\n status: 423,\n error: 'Valid lock token is required for this mutation',\n code: 'record_locked',\n lock: this.toLockView(ownedActiveLock, false, activeLocks),\n }\n }\n }\n\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n shouldReleaseOnSuccess,\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n latestActionLogId: latest?.id ?? null,\n }\n }\n\n const existingConflict = parsedHeaders.conflictId\n ? await this.findConflictById(parsedHeaders.conflictId, input)\n : null\n\n if (existingConflict) {\n const canResolveExistingConflict = existingConflict.status === 'pending'\n && existingConflict.conflictActorUserId === input.userId\n\n if (parsedHeaders.resolution === 'accept_mine' || parsedHeaders.resolution === 'merged') {\n const isAlreadyResolvedByRequester = existingConflict.conflictActorUserId === input.userId\n && existingConflict.status !== 'pending'\n && existingConflict.resolution === parsedHeaders.resolution\n\n if (!canResolveExistingConflict && !isAlreadyResolvedByRequester) {\n return {\n ok: false,\n status: 409,\n error: 'Record conflict requires resolution before saving',\n code: 'record_lock_conflict',\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n conflict: await this.toConflictPayload(existingConflict, input.mutationPayload ?? null, settings.allowIncomingOverride, canOverrideIncoming),\n }\n }\n if (!canOverrideIncoming) {\n return {\n ok: false,\n status: 409,\n error: 'Record conflict requires resolution before saving',\n code: 'record_lock_conflict',\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n conflict: await this.toConflictPayload(existingConflict, input.mutationPayload ?? null, settings.allowIncomingOverride, canOverrideIncoming),\n }\n }\n if (canResolveExistingConflict) {\n await this.resolveConflict(existingConflict, parsedHeaders.resolution, input.userId)\n }\n } else {\n return {\n ok: false,\n status: 409,\n error: 'Record conflict requires resolution before saving',\n code: 'record_lock_conflict',\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n conflict: await this.toConflictPayload(existingConflict, input.mutationPayload ?? null, settings.allowIncomingOverride, canOverrideIncoming),\n }\n }\n }\n\n if (!existingConflict) {\n const baseActionLogId = parsedHeaders.baseLogId\n ?? (ownedActiveLock ? ownedActiveLock.baseActionLogId : null)\n\n const hasConflictingBaseLog = Boolean(\n latest?.id\n && baseActionLogId\n && latest.id !== baseActionLogId\n )\n const hasConflictingWriteAfterLockStart = Boolean(\n latest?.id\n && !baseActionLogId\n && ownedActiveLock\n && latest.createdAt instanceof Date\n && ownedActiveLock.lockedAt instanceof Date\n && latest.createdAt.getTime() > ownedActiveLock.lockedAt.getTime()\n && latest.actorUserId !== input.userId\n )\n const isConflictingWrite = hasConflictingBaseLog || hasConflictingWriteAfterLockStart\n\n if (isConflictingWrite) {\n if (keepMineResolution && canOverrideIncoming) {\n const autoResolvedConflict = await this.createConflict({\n scope: input,\n baseActionLogId,\n incomingActionLogId: latest?.id ?? null,\n conflictActorUserId: input.userId,\n incomingActorUserId: latest?.actorUserId ?? null,\n })\n await this.resolveConflict(autoResolvedConflict, keepMineResolution, input.userId)\n\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n shouldReleaseOnSuccess,\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n latestActionLogId: latest?.id ?? null,\n }\n }\n\n const conflict = await this.createConflict({\n scope: input,\n baseActionLogId,\n incomingActionLogId: latest?.id ?? null,\n conflictActorUserId: input.userId,\n incomingActorUserId: latest?.actorUserId ?? null,\n })\n\n return {\n ok: false,\n status: 409,\n error: 'Record conflict detected',\n code: 'record_lock_conflict',\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n conflict: await this.toConflictPayload(conflict, input.mutationPayload ?? null, settings.allowIncomingOverride, canOverrideIncoming),\n }\n }\n }\n\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n shouldReleaseOnSuccess,\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n latestActionLogId: latest?.id ?? null,\n }\n }\n\n async releaseAfterMutation(input: RecordLockReleaseInput): Promise<void> {\n const releaseResult = await this.release({\n ...input,\n reason: input.reason ?? 'saved',\n })\n if (!releaseResult.released) return\n }\n\n async emitIncomingChangesNotificationAfterMutation(input: {\n tenantId: string\n organizationId?: string | null\n userId: string\n resourceKind: string\n resourceId: string\n method: 'PUT' | 'DELETE'\n }): Promise<void> {\n if (input.method !== 'PUT') return\n const settings = await this.getSettings()\n if (!settings.notifyOnConflict || !isRecordLockingEnabledForResource(settings, input.resourceKind)) return\n\n const now = new Date()\n let activeLocks = await this.findActiveLocks(input, now)\n if (!activeLocks.length) {\n const fallbackWhere: FilterQuery<RecordLock> = {\n tenantId: input.tenantId,\n deletedAt: null,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n status: ACTIVE_LOCK_STATUS,\n }\n const fallbackLocks = await this.em.find(RecordLock, fallbackWhere, { orderBy: { updatedAt: 'desc' } })\n activeLocks = Array.isArray(fallbackLocks) ? fallbackLocks : []\n }\n\n const recipientUserIds = new Set<string>()\n for (const lock of activeLocks) {\n if (lock.lockedByUserId !== input.userId) {\n recipientUserIds.add(lock.lockedByUserId)\n }\n }\n if (!recipientUserIds.size) {\n const fallbackWindowMs = Math.max((settings.timeoutSeconds ?? 300) * 1000, 60_000)\n const fallbackSince = new Date(now.getTime() - fallbackWindowMs)\n const recentLocks = await this.em.find(RecordLock, {\n tenantId: input.tenantId,\n deletedAt: null,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n updatedAt: { $gte: fallbackSince },\n }, { orderBy: { updatedAt: 'desc' }, limit: 50 })\n\n for (const lock of (Array.isArray(recentLocks) ? recentLocks : [])) {\n if (lock.lockedByUserId !== input.userId) {\n recipientUserIds.add(lock.lockedByUserId)\n }\n }\n }\n if (!recipientUserIds.size) return\n\n let latest = await this.findLatestActionLog(input)\n if (!latest) {\n latest = await this.findLatestActionLog({\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n })\n }\n let actorLog = latest?.actorUserId === input.userId\n ? latest\n : await this.findLatestActionLogByActor(input, input.userId)\n if (!actorLog) {\n actorLog = await this.findLatestActionLogByActor({\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n }, input.userId)\n }\n const incomingLog = actorLog ?? latest\n\n const changedFields = incomingLog\n ? this.summarizeChangedFieldsFromActionLog(incomingLog)\n : ''\n const changedRows = this.buildIncomingChangeRowsFromActionLog(incomingLog)\n const changedRowsJson = changedRows.length ? JSON.stringify(changedRows) : ''\n\n await emitRecordLocksEvent('record_locks.incoming_changes.available', {\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n incomingActorUserId: input.userId,\n incomingActionLogId: incomingLog?.id ?? null,\n recipientUserIds: Array.from(recipientUserIds),\n changedFields: changedFields || '-',\n changedRowsJson,\n })\n }\n\n async emitRecordDeletedNotificationAfterMutation(input: {\n tenantId: string\n organizationId?: string | null\n userId: string\n resourceKind: string\n resourceId: string\n method: 'PUT' | 'DELETE'\n }): Promise<void> {\n if (input.method !== 'DELETE') return\n const settings = await this.getSettings()\n if (!isRecordLockingEnabledForResource(settings, input.resourceKind)) return\n\n const now = new Date()\n let activeLocks = await this.findActiveLocks(input, now)\n if (!activeLocks.length) {\n const fallbackWhere: FilterQuery<RecordLock> = {\n tenantId: input.tenantId,\n deletedAt: null,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n status: ACTIVE_LOCK_STATUS,\n }\n const fallbackLocks = await this.em.find(RecordLock, fallbackWhere, { orderBy: { updatedAt: 'desc' } })\n activeLocks = Array.isArray(fallbackLocks) ? fallbackLocks : []\n }\n\n const recipientUserIds = new Set<string>()\n for (const lock of activeLocks) {\n if (lock.lockedByUserId !== input.userId) {\n recipientUserIds.add(lock.lockedByUserId)\n }\n }\n\n if (!recipientUserIds.size) {\n const fallbackWindowMs = Math.max((settings.timeoutSeconds ?? 300) * 1000, 60_000)\n const fallbackSince = new Date(now.getTime() - fallbackWindowMs)\n const recentLocks = await this.em.find(RecordLock, {\n tenantId: input.tenantId,\n deletedAt: null,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n updatedAt: { $gte: fallbackSince },\n }, { orderBy: { updatedAt: 'desc' }, limit: 50 })\n\n for (const lock of (Array.isArray(recentLocks) ? recentLocks : [])) {\n if (lock.lockedByUserId !== input.userId) {\n recipientUserIds.add(lock.lockedByUserId)\n }\n }\n }\n if (!recipientUserIds.size) return\n\n await emitRecordLocksEvent('record_locks.record.deleted', {\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n deletedByUserId: input.userId,\n recipientUserIds: Array.from(recipientUserIds),\n })\n }\n\n async resolveConflictById(input: {\n conflictId: string\n tenantId: string\n organizationId?: string | null\n userId: string\n resolution: 'accept_incoming' | 'accept_mine' | 'merged'\n }): Promise<boolean> {\n const settings = await this.getSettings()\n const canOverrideIncoming = await this.canUserOverrideIncoming(input, settings)\n const conflict = await this.em.findOne(RecordLockConflict, {\n id: input.conflictId,\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n deletedAt: null,\n })\n if (!conflict || conflict.status !== 'pending' || conflict.conflictActorUserId !== input.userId) {\n return false\n }\n if ((input.resolution === 'accept_mine' || input.resolution === 'merged') && !canOverrideIncoming) {\n return false\n }\n await this.resolveConflict(conflict, input.resolution, input.userId)\n return true\n }\n\n private async canUserOverrideIncoming(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId' | 'userId'>,\n settings: RecordLockSettings,\n ): Promise<boolean> {\n if (!settings.allowIncomingOverride) return false\n if (!this.rbacService) return false\n\n try {\n return await this.rbacService.userHasAllFeatures(\n input.userId,\n ['record_locks.override_incoming'],\n {\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n },\n )\n } catch {\n return false\n }\n }\n\n private async canUserForceRelease(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId' | 'userId'>,\n settings: RecordLockSettings,\n ): Promise<boolean> {\n if (!settings.allowForceUnlock) return false\n if (!this.rbacService) return false\n\n try {\n return await this.rbacService.userHasAllFeatures(\n input.userId,\n ['record_locks.force_release'],\n {\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n },\n )\n } catch {\n return false\n }\n }\n\n private pruneLockCleanupState(now: number): void {\n for (const [tenantId, state] of lockCleanupStateByTenant.entries()) {\n if (!state.inFlight && now - state.lastSeenAt > LOCK_CLEANUP_STATE_TTL_MS) {\n lockCleanupStateByTenant.delete(tenantId)\n }\n }\n\n if (lockCleanupStateByTenant.size <= LOCK_CLEANUP_STATE_MAX_ENTRIES) return\n\n const removable = Array.from(lockCleanupStateByTenant.entries())\n .filter(([, state]) => !state.inFlight)\n .sort((left, right) => left[1].lastSeenAt - right[1].lastSeenAt)\n const overflow = lockCleanupStateByTenant.size - LOCK_CLEANUP_STATE_MAX_ENTRIES\n for (const [tenantId] of removable.slice(0, Math.max(0, overflow))) {\n lockCleanupStateByTenant.delete(tenantId)\n }\n }\n\n private scheduleCleanup(tenantId: string): void {\n const now = Date.now()\n this.pruneLockCleanupState(now)\n const state = lockCleanupStateByTenant.get(tenantId) ?? { lastRunAt: 0, inFlight: false, lastSeenAt: now }\n state.lastSeenAt = now\n lockCleanupStateByTenant.set(tenantId, state)\n if (state.inFlight) return\n if (now - state.lastRunAt < LOCK_CLEANUP_INTERVAL_MS) return\n\n state.inFlight = true\n state.lastRunAt = now\n lockCleanupStateByTenant.set(tenantId, state)\n\n void this.cleanupHistoricalRecords(tenantId).finally(() => {\n const current = lockCleanupStateByTenant.get(tenantId)\n if (!current) return\n current.inFlight = false\n lockCleanupStateByTenant.set(tenantId, current)\n })\n }\n\n private async cleanupHistoricalRecords(tenantId: string): Promise<void> {\n try {\n const db = getKysely(this.em)\n const now = Date.now()\n const lockCutoff = new Date(now - LOCK_RETENTION_MS)\n const resolvedConflictCutoff = new Date(now - RESOLVED_CONFLICT_RETENTION_MS)\n const pendingConflictCutoff = new Date(now - PENDING_CONFLICT_RETENTION_MS)\n const deletedAt = new Date(now)\n\n await db\n .updateTable('record_locks' as any)\n .set({\n deleted_at: deletedAt,\n updated_at: deletedAt,\n } as any)\n .where('tenant_id' as any, '=', tenantId)\n .where('deleted_at' as any, 'is', null as any)\n .where('status' as any, '!=', ACTIVE_LOCK_STATUS)\n .where('updated_at' as any, '<', lockCutoff)\n .execute()\n\n await db\n .updateTable('record_lock_conflicts' as any)\n .set({\n deleted_at: deletedAt,\n updated_at: deletedAt,\n } as any)\n .where('tenant_id' as any, '=', tenantId)\n .where('deleted_at' as any, 'is', null as any)\n .where((eb: any) => eb.or([\n eb.and([\n eb('status' as any, '=', 'pending'),\n eb('created_at' as any, '<', pendingConflictCutoff),\n ]),\n eb.and([\n eb('status' as any, '!=', 'pending'),\n eb('updated_at' as any, '<', resolvedConflictCutoff),\n ]),\n ]))\n .execute()\n } catch {\n // Best-effort cleanup must never fail lock workflows.\n }\n }\n\n private normalizeMutationHeaders(headers: Partial<RecordLockMutationHeaders>): Partial<RecordLockMutationHeaders> {\n const parsed = recordLockMutationHeaderSchema.partial().safeParse(headers)\n if (!parsed.success) return {}\n return parsed.data\n }\n\n private buildScopeWhere(scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'>): {\n tenantId: string\n deletedAt: null\n organizationId?: string | null\n } {\n const where: {\n tenantId: string\n deletedAt: null\n organizationId?: string | null\n } = {\n tenantId: scope.tenantId,\n deletedAt: null,\n }\n\n if (scope.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(scope.organizationId)\n }\n\n return where\n }\n\n private async findActiveLocks(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n now: Date,\n ): Promise<RecordLock[]> {\n const legacyFinder = (this as unknown as {\n findActiveLock?: (args: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource, at: Date) => Promise<RecordLock | null>\n }).findActiveLock\n if (typeof legacyFinder === 'function') {\n const legacyResult = await legacyFinder(input, now)\n return legacyResult ? [legacyResult] : []\n }\n\n const where: FilterQuery<RecordLock> = {\n ...this.buildScopeWhere(input),\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n status: ACTIVE_LOCK_STATUS,\n }\n\n const locks = await this.em.find(RecordLock, where, { orderBy: { updatedAt: 'desc' } })\n if (!Array.isArray(locks) || !locks.length) return []\n\n let dirty = false\n const active: RecordLock[] = []\n const expiredLocks: RecordLock[] = []\n\n for (const lock of locks) {\n if (lock.expiresAt <= now) {\n this.markLockReleased(lock, {\n status: 'expired',\n reason: 'expired',\n releasedByUserId: lock.lockedByUserId,\n now,\n })\n dirty = true\n expiredLocks.push(lock)\n continue\n }\n\n active.push(lock)\n }\n\n if (dirty) await this.em.flush()\n if (expiredLocks.length) {\n const recipientUserIds = active.map((lock) => lock.lockedByUserId)\n for (const expiredLock of expiredLocks) {\n await emitRecordLocksEvent('record_locks.participant.left', {\n lockId: expiredLock.id,\n resourceKind: expiredLock.resourceKind,\n resourceId: expiredLock.resourceId,\n tenantId: expiredLock.tenantId,\n organizationId: expiredLock.organizationId,\n leftUserId: expiredLock.lockedByUserId,\n reason: 'expired',\n recipientUserIds,\n activeParticipantCount: active.length,\n })\n }\n }\n return active\n }\n\n private async findOwnedLockByToken(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId' | 'userId'> & RecordLockResource & { token?: string },\n ): Promise<RecordLock | null> {\n if (!input.token) return null\n\n const where: FilterQuery<RecordLock> = {\n ...this.buildScopeWhere(input),\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n token: input.token,\n lockedByUserId: input.userId,\n status: ACTIVE_LOCK_STATUS,\n }\n\n return this.em.findOne(RecordLock, where)\n }\n\n private async findOwnedActiveLock(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId' | 'userId'> & RecordLockResource,\n ): Promise<RecordLock | null> {\n const where: FilterQuery<RecordLock> = {\n ...this.buildScopeWhere(input),\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n lockedByUserId: input.userId,\n status: ACTIVE_LOCK_STATUS,\n }\n return this.em.findOne(RecordLock, where)\n }\n\n private async hasRecentSavedRelease(input: {\n tenantId: string\n organizationId?: string | null\n userId: string\n resourceKind: string\n resourceId: string\n now: Date\n }): Promise<boolean> {\n const cutoff = new Date(input.now.getTime() - PARTICIPANT_REJOIN_AFTER_SAVE_SUPPRESS_MS)\n const where: FilterQuery<RecordLock> = {\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n lockedByUserId: input.userId,\n status: 'released',\n releaseReason: 'saved',\n deletedAt: null,\n releasedAt: { $gte: cutoff },\n }\n if (input.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(input.organizationId)\n }\n\n const scoped = await this.em.findOne(RecordLock, where, { orderBy: { releasedAt: 'desc' } })\n if (scoped) return true\n if (input.organizationId === undefined) return false\n\n return Boolean(await this.em.findOne(RecordLock, {\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n lockedByUserId: input.userId,\n status: 'released',\n releaseReason: 'saved',\n deletedAt: null,\n releasedAt: { $gte: cutoff },\n }, { orderBy: { releasedAt: 'desc' } }))\n }\n\n private markLockReleased(\n lock: RecordLock,\n params: {\n status: RecordLockStatus\n reason: RecordLockReleaseReason\n releasedByUserId: string\n now: Date\n },\n ) {\n lock.status = params.status\n lock.releaseReason = params.reason\n lock.releasedByUserId = params.releasedByUserId\n lock.releasedAt = params.now\n lock.updatedAt = params.now\n }\n\n private async findLatestActionLog(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n ): Promise<ActionLog | null> {\n const where: FilterQuery<ActionLog> = {\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n deletedAt: null,\n }\n\n if (input.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(input.organizationId)\n }\n\n return this.em.findOne(ActionLog, where, { orderBy: { createdAt: 'desc' } })\n }\n\n private async findLatestActionLogWithScopeFallback(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n ): Promise<ActionLog | null> {\n const scoped = await this.findLatestActionLog(input)\n if (scoped) return scoped\n if (input.organizationId !== null) return null\n\n return this.findLatestActionLog({\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n })\n }\n\n private async findLatestActionLogByActor(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n actorUserId: string,\n ): Promise<ActionLog | null> {\n const where: FilterQuery<ActionLog> = {\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n actorUserId,\n deletedAt: null,\n }\n\n if (input.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(input.organizationId)\n }\n\n return this.em.findOne(ActionLog, where, { orderBy: { createdAt: 'desc' } })\n }\n\n private summarizeChangedFieldsFromActionLog(log: ActionLog | null): string {\n if (!log) return ''\n\n if (isRecordValue(log.changesJson)) {\n const fromChanges = Object.keys(log.changesJson)\n .filter((field) => !shouldSkipConflictField(field))\n .slice(0, 12)\n .map(formatChangedFieldLabel)\n .join(', ')\n if (fromChanges) return fromChanges\n }\n\n const before = isRecordValue(log.snapshotBefore) ? log.snapshotBefore : null\n const after = isRecordValue(log.snapshotAfter) ? log.snapshotAfter : null\n if (!before || !after) return ''\n\n const diffPaths = new Set<string>()\n this.collectSnapshotDiffPaths(before, after, null, diffPaths, new Set<unknown>())\n\n return Array.from(diffPaths)\n .filter((field) => !shouldSkipConflictField(field))\n .sort((left, right) => left.localeCompare(right))\n .slice(0, 12)\n .map(formatChangedFieldLabel)\n .join(', ')\n }\n\n private buildIncomingChangeRowsFromActionLog(log: ActionLog | null): Array<{\n field: string\n incoming: string\n current: string\n }> {\n if (!log || !isRecordValue(log.changesJson)) return []\n\n const rows: Array<{ field: string; incoming: string; current: string }> = []\n for (const [rawField, rawChange] of Object.entries(log.changesJson)) {\n if (rows.length >= 12) break\n if (shouldSkipConflictField(rawField)) continue\n\n const change = isRecordValue(rawChange) ? rawChange : {}\n const incoming = Object.prototype.hasOwnProperty.call(change, 'to')\n ? formatNotificationValue(change.to)\n : '-'\n const current = Object.prototype.hasOwnProperty.call(change, 'from')\n ? formatNotificationValue(change.from)\n : '-'\n\n rows.push({\n field: formatChangedFieldLabel(rawField),\n incoming,\n current,\n })\n }\n\n return rows\n }\n\n private toParticipantView(lock: RecordLock): RecordLockParticipantView {\n return {\n userId: lock.lockedByUserId,\n lockedByIp: lock.lockedByIp ?? null,\n lockedAt: normalizeDate(lock.lockedAt),\n lastHeartbeatAt: normalizeDate(lock.lastHeartbeatAt),\n expiresAt: normalizeDate(lock.expiresAt),\n }\n }\n\n private sortLocksByJoinOrder(locks: RecordLock[]): RecordLock[] {\n return [...locks].sort((left, right) => {\n const leftLockedAt = left.lockedAt instanceof Date ? left.lockedAt.getTime() : 0\n const rightLockedAt = right.lockedAt instanceof Date ? right.lockedAt.getTime() : 0\n if (leftLockedAt !== rightLockedAt) return leftLockedAt - rightLockedAt\n\n const leftCreatedAt = left.createdAt instanceof Date ? left.createdAt.getTime() : 0\n const rightCreatedAt = right.createdAt instanceof Date ? right.createdAt.getTime() : 0\n if (leftCreatedAt !== rightCreatedAt) return leftCreatedAt - rightCreatedAt\n\n return left.id.localeCompare(right.id)\n })\n }\n\n private toLockView(lock: RecordLock, includeToken: boolean, activeLocks: RecordLock[] = [lock]): RecordLockView {\n const participants = activeLocks.map((item) => this.toParticipantView(item))\n return {\n id: lock.id,\n resourceKind: lock.resourceKind,\n resourceId: lock.resourceId,\n token: includeToken ? lock.token : null,\n strategy: lock.strategy,\n status: lock.status,\n lockedByUserId: lock.lockedByUserId,\n lockedByIp: lock.lockedByIp ?? null,\n baseActionLogId: lock.baseActionLogId,\n lockedAt: normalizeDate(lock.lockedAt),\n lastHeartbeatAt: normalizeDate(lock.lastHeartbeatAt),\n expiresAt: normalizeDate(lock.expiresAt),\n participants,\n activeParticipantCount: participants.length,\n }\n }\n\n private async createConflict(input: {\n scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource\n baseActionLogId: string | null\n incomingActionLogId: string | null\n conflictActorUserId: string\n incomingActorUserId: string | null\n }): Promise<RecordLockConflict> {\n const dedupeKey = [\n 'record_locks',\n 'conflict',\n input.scope.tenantId,\n normalizeScopeOrganization(input.scope.organizationId) ?? 'global',\n input.scope.resourceKind,\n input.scope.resourceId,\n input.conflictActorUserId,\n input.baseActionLogId ?? 'none',\n input.incomingActionLogId ?? 'none',\n ].join(':')\n\n const result = await this.em.transactional(async (tx) => {\n try {\n const db = getKysely(tx as EntityManager)\n await sql`select pg_advisory_xact_lock(hashtext(${dedupeKey}))`.execute(db)\n } catch {\n // Best-effort lock; fallback to find-first behavior below.\n }\n\n const existing = await this.findPendingConflictByFingerprint(tx as EntityManager, input)\n if (existing) {\n return { conflict: existing, created: false as const }\n }\n\n const conflict = tx.create(RecordLockConflict, {\n resourceKind: input.scope.resourceKind,\n resourceId: input.scope.resourceId,\n status: 'pending',\n resolution: null,\n baseActionLogId: input.baseActionLogId,\n incomingActionLogId: input.incomingActionLogId,\n conflictActorUserId: input.conflictActorUserId,\n incomingActorUserId: input.incomingActorUserId,\n tenantId: input.scope.tenantId,\n organizationId: normalizeScopeOrganization(input.scope.organizationId),\n })\n\n tx.persist(conflict)\n await tx.flush()\n return { conflict, created: true as const }\n })\n\n if (result.created) {\n await emitRecordLocksEvent('record_locks.conflict.detected', {\n conflictId: result.conflict.id,\n resourceKind: result.conflict.resourceKind,\n resourceId: result.conflict.resourceId,\n tenantId: result.conflict.tenantId,\n organizationId: result.conflict.organizationId,\n conflictActorUserId: result.conflict.conflictActorUserId,\n incomingActorUserId: result.conflict.incomingActorUserId,\n baseActionLogId: result.conflict.baseActionLogId,\n incomingActionLogId: result.conflict.incomingActionLogId,\n })\n }\n\n return result.conflict\n }\n\n private findPendingConflictByFingerprint(\n em: EntityManager,\n input: {\n scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource\n baseActionLogId: string | null\n incomingActionLogId: string | null\n conflictActorUserId: string\n },\n ): Promise<RecordLockConflict | null> {\n return em.findOne(RecordLockConflict, {\n tenantId: input.scope.tenantId,\n organizationId: normalizeScopeOrganization(input.scope.organizationId),\n resourceKind: input.scope.resourceKind,\n resourceId: input.scope.resourceId,\n conflictActorUserId: input.conflictActorUserId,\n status: 'pending',\n baseActionLogId: input.baseActionLogId,\n incomingActionLogId: input.incomingActionLogId,\n deletedAt: null,\n }, { orderBy: { createdAt: 'desc' } })\n }\n\n private async resolveConflict(\n conflict: RecordLockConflict,\n resolution: 'accept_incoming' | Extract<RecordLockMutationHeaders['resolution'], 'accept_mine' | 'merged'>,\n resolvedByUserId: string,\n ): Promise<void> {\n const now = new Date()\n\n const resolutionMap: Record<'accept_incoming' | Extract<RecordLockMutationHeaders['resolution'], 'accept_mine' | 'merged'>, {\n status: RecordLockConflictStatus\n resolution: RecordLockConflictResolution\n }> = {\n accept_incoming: { status: 'resolved_accept_incoming', resolution: 'accept_incoming' },\n accept_mine: { status: 'resolved_accept_mine', resolution: 'accept_mine' },\n merged: { status: 'resolved_merged', resolution: 'merged' },\n }\n\n const target = resolutionMap[resolution]\n conflict.status = target.status\n conflict.resolution = target.resolution\n conflict.resolvedByUserId = resolvedByUserId\n conflict.resolvedAt = now\n conflict.updatedAt = now\n await this.em.flush()\n\n await emitRecordLocksEvent('record_locks.conflict.resolved', {\n conflictId: conflict.id,\n resourceKind: conflict.resourceKind,\n resourceId: conflict.resourceId,\n tenantId: conflict.tenantId,\n organizationId: conflict.organizationId,\n conflictActorUserId: conflict.conflictActorUserId,\n incomingActorUserId: conflict.incomingActorUserId,\n resolution: conflict.resolution,\n resolvedByUserId,\n })\n }\n\n private async findConflictById(\n conflictId: string,\n scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n ): Promise<RecordLockConflict | null> {\n const where: FilterQuery<RecordLockConflict> = {\n id: conflictId,\n tenantId: scope.tenantId,\n resourceKind: scope.resourceKind,\n resourceId: scope.resourceId,\n deletedAt: null,\n }\n\n if (scope.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(scope.organizationId)\n }\n\n const scoped = await this.em.findOne(RecordLockConflict, where)\n if (scoped || scope.organizationId === undefined) return scoped\n\n return this.em.findOne(RecordLockConflict, {\n id: conflictId,\n tenantId: scope.tenantId,\n resourceKind: scope.resourceKind,\n resourceId: scope.resourceId,\n deletedAt: null,\n })\n }\n\n private async findActionLogById(\n logId: string | null,\n scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n ): Promise<ActionLog | null> {\n if (!logId) return null\n\n let resolved = this.actionLogService\n ? await this.actionLogService.findById(logId)\n : null\n if (!resolved) {\n resolved = await this.em.findOne(ActionLog, { id: logId, deletedAt: null })\n }\n if (!resolved || resolved.deletedAt) return null\n\n if (resolved.tenantId !== scope.tenantId) return null\n\n if (scope.organizationId !== undefined) {\n const expectedOrganizationId = normalizeScopeOrganization(scope.organizationId)\n if (normalizeScopeOrganization(resolved.organizationId) !== expectedOrganizationId) return null\n }\n\n if (resolved.resourceKind !== scope.resourceKind || resolved.resourceId !== scope.resourceId) {\n return null\n }\n\n return resolved\n }\n\n private collectSnapshotDiffPaths(\n before: unknown,\n after: unknown,\n pathPrefix: string | null,\n output: Set<string>,\n seen: Set<unknown>,\n ): void {\n if (valuesEqual(before, after)) return\n\n const beforeRecord = isRecordValue(before) ? before : null\n const afterRecord = isRecordValue(after) ? after : null\n\n if (!beforeRecord || !afterRecord) {\n if (pathPrefix) output.add(pathPrefix)\n return\n }\n\n if (seen.has(beforeRecord) || seen.has(afterRecord)) {\n if (pathPrefix) output.add(pathPrefix)\n return\n }\n\n seen.add(beforeRecord)\n seen.add(afterRecord)\n\n const keys = new Set([...Object.keys(beforeRecord), ...Object.keys(afterRecord)])\n for (const key of keys) {\n if (SKIPPED_CONFLICT_FIELDS.has(key)) continue\n const nextPath = pathPrefix ? `${pathPrefix}.${key}` : key\n this.collectSnapshotDiffPaths(beforeRecord[key], afterRecord[key], nextPath, output, seen)\n }\n }\n\n private async buildConflictChanges(\n conflict: RecordLockConflict,\n mutationPayload: Record<string, unknown> | null,\n ): Promise<RecordLockConflictChange[]> {\n const scope = {\n tenantId: conflict.tenantId,\n organizationId: conflict.organizationId,\n resourceKind: conflict.resourceKind,\n resourceId: conflict.resourceId,\n }\n\n const baseLog = await this.findActionLogById(conflict.baseActionLogId, scope)\n const incomingLog = await this.findActionLogById(conflict.incomingActionLogId, scope)\n\n const baseSnapshot = isRecordValue(baseLog?.snapshotAfter) ? baseLog.snapshotAfter : null\n const incomingBeforeSnapshot = isRecordValue(incomingLog?.snapshotBefore) ? incomingLog.snapshotBefore : null\n const incomingAfterSnapshot = isRecordValue(incomingLog?.snapshotAfter) ? incomingLog.snapshotAfter : null\n const fallbackBaseSnapshot = baseSnapshot ?? incomingBeforeSnapshot\n\n const changeMap = new Map<string, { baseValue: unknown; incomingValue: unknown }>()\n\n const incomingChanges = isRecordValue(incomingLog?.changesJson) ? incomingLog.changesJson : null\n if (incomingChanges) {\n for (const [fieldPathRaw, rawChange] of Object.entries(incomingChanges)) {\n const fieldPath = fieldPathRaw.trim()\n if (shouldSkipConflictField(fieldPath)) continue\n\n const changeRecord = isRecordValue(rawChange) ? rawChange : null\n const fromValue = changeRecord && Object.prototype.hasOwnProperty.call(changeRecord, 'from')\n ? changeRecord.from\n : readPathValueLoose(fallbackBaseSnapshot, fieldPath)\n const toValue = changeRecord && Object.prototype.hasOwnProperty.call(changeRecord, 'to')\n ? changeRecord.to\n : readPathValueLoose(incomingAfterSnapshot, fieldPath)\n\n changeMap.set(fieldPath, {\n baseValue: fromValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(fromValue),\n incomingValue: toValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(toValue),\n })\n }\n }\n\n if (!changeMap.size && fallbackBaseSnapshot && incomingAfterSnapshot) {\n const diffPaths = new Set<string>()\n this.collectSnapshotDiffPaths(\n fallbackBaseSnapshot,\n incomingAfterSnapshot,\n null,\n diffPaths,\n new Set<unknown>(),\n )\n\n for (const fieldPath of diffPaths) {\n if (shouldSkipConflictField(fieldPath)) continue\n const fromValue = readPathValueLoose(fallbackBaseSnapshot, fieldPath)\n const toValue = readPathValueLoose(incomingAfterSnapshot, fieldPath)\n changeMap.set(fieldPath, {\n baseValue: fromValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(fromValue),\n incomingValue: toValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(toValue),\n })\n }\n }\n\n if (!changeMap.size && mutationPayload && incomingAfterSnapshot) {\n for (const fieldPath of Object.keys(mutationPayload)) {\n if (shouldSkipConflictField(fieldPath)) continue\n const mineValue = readPathValueLoose(mutationPayload, fieldPath)\n const incomingValue = readPathValueLoose(incomingAfterSnapshot, fieldPath)\n if (mineValue === MISSING_CONFLICT_VALUE || incomingValue === MISSING_CONFLICT_VALUE) continue\n if (valuesEqual(mineValue, incomingValue)) continue\n const baseValue = readPathValueLoose(fallbackBaseSnapshot, fieldPath)\n changeMap.set(fieldPath, {\n baseValue: baseValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(baseValue),\n incomingValue: normalizeConflictValue(incomingValue),\n })\n }\n }\n\n if (!changeMap.size) return []\n\n const allFields = Array.from(changeMap.keys())\n const preferredFields = mutationPayload\n ? allFields.filter((fieldPath) => {\n const mineValue = readPathValueLoose(mutationPayload, fieldPath)\n if (mineValue === MISSING_CONFLICT_VALUE) return false\n const incomingValue = changeMap.get(fieldPath)?.incomingValue\n return !valuesEqual(mineValue, incomingValue)\n })\n : []\n const selectedFields = (preferredFields.length ? preferredFields : allFields)\n .filter((fieldPath) => !shouldSkipConflictField(fieldPath))\n .sort((left, right) => left.localeCompare(right))\n .slice(0, 25)\n\n return selectedFields.map((fieldPath) => {\n const entry = changeMap.get(fieldPath) ?? { baseValue: null, incomingValue: null }\n const mineValueRaw = mutationPayload ? readPathValueLoose(mutationPayload, fieldPath) : MISSING_CONFLICT_VALUE\n const mineValue = mineValueRaw === MISSING_CONFLICT_VALUE\n ? entry.baseValue\n : normalizeConflictValue(mineValueRaw)\n\n return {\n field: fieldPath,\n displayValue: normalizeConflictValue(entry.baseValue),\n baseValue: normalizeConflictValue(entry.baseValue),\n incomingValue: normalizeConflictValue(entry.incomingValue),\n mineValue: normalizeConflictValue(mineValue),\n }\n })\n }\n\n private async toConflictPayload(\n conflict: RecordLockConflict,\n mutationPayload: Record<string, unknown> | null,\n allowIncomingOverride: boolean,\n canOverrideIncoming: boolean,\n ): Promise<RecordLockConflictPayload> {\n const changes = await this.buildConflictChanges(conflict, mutationPayload)\n return {\n id: conflict.id,\n resourceKind: conflict.resourceKind,\n resourceId: conflict.resourceId,\n baseActionLogId: conflict.baseActionLogId,\n incomingActionLogId: conflict.incomingActionLogId,\n allowIncomingOverride,\n canOverrideIncoming,\n resolutionOptions: canOverrideIncoming ? ['accept_mine'] : [],\n changes,\n }\n }\n}\n\nexport function createRecordLockService(deps: RecordLockServiceDeps): RecordLockService {\n return new RecordLockService(deps)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { randomUUID } from 'crypto'\nimport { UniqueConstraintViolationException, type FilterQuery } from '@mikro-orm/core'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { type Kysely, sql } from 'kysely'\nimport type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib/module-config-service'\nimport { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { emitRecordLocksEvent } from '../events'\nimport {\n RecordLock,\n RecordLockConflict,\n type RecordLockStatus,\n type RecordLockReleaseReason,\n type RecordLockConflictResolution,\n type RecordLockConflictStatus,\n} from '../data/entities'\nimport {\n recordLockMutationHeaderSchema,\n type RecordLockMutationHeaders,\n type RecordLockReleaseInput as RecordLockReleasePayloadInput,\n type RecordLockSettingsInput,\n} from '../data/validators'\nimport {\n DEFAULT_RECORD_LOCK_SETTINGS,\n RECORD_LOCKS_MODULE_ID,\n RECORD_LOCKS_SETTINGS_NAME,\n isRecordLockingEnabledForResource,\n normalizeRecordLockSettings,\n type RecordLockSettings,\n type RecordLockStrategy,\n} from './config'\n\nconst ACTIVE_LOCK_STATUS: RecordLockStatus = 'active'\nconst ACTIVE_SCOPE_UNIQUE_CONSTRAINTS = new Set([\n 'record_locks_active_scope_org_unique',\n 'record_locks_active_scope_tenant_unique',\n 'record_locks_active_scope_user_org_unique',\n 'record_locks_active_scope_user_tenant_unique',\n])\nconst LOCK_CONTENTION_EVENT_TTL_MS = 15_000\nconst PARTICIPANT_REJOIN_AFTER_SAVE_SUPPRESS_MS = 20_000\nconst LOCK_CONTENTION_EVENT_MAX_ENTRIES = 2_000\nconst lockContentionEventThrottle = new Map<string, number>()\nconst LOCK_CLEANUP_INTERVAL_MS = 5 * 60 * 1000\nconst LOCK_RETENTION_MS = 3 * 24 * 60 * 60 * 1000\nconst RESOLVED_CONFLICT_RETENTION_MS = 7 * 24 * 60 * 60 * 1000\nconst PENDING_CONFLICT_RETENTION_MS = 24 * 60 * 60 * 1000\nconst LOCK_CLEANUP_STATE_TTL_MS = 24 * 60 * 60 * 1000\nconst LOCK_CLEANUP_STATE_MAX_ENTRIES = 2_000\nconst lockCleanupStateByTenant = new Map<string, { lastRunAt: number; inFlight: boolean; lastSeenAt: number }>()\n\nexport type RecordLockScope = {\n tenantId: string\n organizationId?: string | null\n userId: string\n}\n\nexport type RecordLockResource = {\n resourceKind: string\n resourceId: string\n}\n\nexport type RecordLockAcquireInput = RecordLockScope & RecordLockResource & {\n lockedByIp?: string | null\n}\n\nexport type RecordLockHeartbeatInput = RecordLockScope & RecordLockResource & {\n token: string\n}\n\nexport type RecordLockReleaseInput = RecordLockScope & RecordLockResource & {\n token?: string\n reason?: Exclude<RecordLockReleaseReason, 'expired' | 'force'>\n} & Pick<RecordLockReleasePayloadInput, 'conflictId' | 'resolution'>\n\nexport type RecordLockForceReleaseInput = RecordLockScope & RecordLockResource & {\n reason?: string | null\n}\n\nexport type RecordLockMutationValidationInput = RecordLockScope & RecordLockResource & {\n method: 'PUT' | 'DELETE'\n headers: Partial<RecordLockMutationHeaders>\n mutationPayload?: Record<string, unknown> | null\n}\n\nexport type RecordLockConflictChange = {\n field: string\n displayValue: unknown\n baseValue: unknown\n incomingValue: unknown\n mineValue: unknown\n}\n\nexport type RecordLockConflictPayload = {\n id: string\n resourceKind: string\n resourceId: string\n baseActionLogId: string | null\n incomingActionLogId: string | null\n allowIncomingOverride: boolean\n canOverrideIncoming: boolean\n resolutionOptions: Array<'accept_mine'>\n changes: RecordLockConflictChange[]\n}\n\nexport type RecordLockView = {\n id: string\n resourceKind: string\n resourceId: string\n token: string | null\n strategy: RecordLockStrategy\n status: RecordLockStatus\n lockedByUserId: string\n lockedByIp: string | null\n baseActionLogId: string | null\n lockedAt: string\n lastHeartbeatAt: string\n expiresAt: string\n participants: RecordLockParticipantView[]\n activeParticipantCount: number\n}\n\nexport type RecordLockParticipantView = {\n userId: string\n lockedByIp: string | null\n lockedAt: string\n lastHeartbeatAt: string\n expiresAt: string\n}\n\nexport type RecordLockAcquireResult = {\n ok: true\n enabled: boolean\n resourceEnabled: boolean\n strategy: RecordLockStrategy\n allowForceUnlock: boolean\n heartbeatSeconds: number\n acquired: boolean\n latestActionLogId: string | null\n lock: RecordLockView | null\n}\n\nexport type RecordLockAcquireFailure = RecordLockValidationFailure & {\n allowForceUnlock: boolean\n}\n\nexport type RecordLockHeartbeatResult = {\n ok: true\n expiresAt: string | null\n}\n\nexport type RecordLockReleaseResult = {\n ok: true\n released: boolean\n conflictResolved: boolean\n}\n\nexport type RecordLockForceReleaseResult = {\n ok: true\n released: boolean\n lock: RecordLockView | null\n}\n\nexport type RecordLockValidationSuccess = {\n ok: true\n enabled: boolean\n resourceEnabled: boolean\n strategy: RecordLockStrategy\n shouldReleaseOnSuccess: boolean\n lock: RecordLockView | null\n latestActionLogId: string | null\n}\n\nexport type RecordLockValidationFailure = {\n ok: false\n status: 409 | 423\n error: string\n code: 'record_lock_conflict' | 'record_locked'\n lock: RecordLockView | null\n conflict?: RecordLockConflictPayload\n}\n\nexport type RecordLockValidationResult = RecordLockValidationSuccess | RecordLockValidationFailure\n\nexport type RecordLockServiceDeps = {\n em: EntityManager\n moduleConfigService?: ModuleConfigService | null\n actionLogService?: ActionLogService | null\n rbacService?: RbacService | null\n}\n\nexport type ParsedRecordLockHeaders = Partial<RecordLockMutationHeaders>\n\nfunction normalizeDate(value: Date): string {\n return value.toISOString()\n}\n\nfunction trimToNull(value: string | null | undefined): string | null {\n if (typeof value !== 'string') return null\n const trimmed = value.trim()\n return trimmed.length ? trimmed : null\n}\n\nfunction normalizeScopeOrganization(value: string | null | undefined): string | null {\n const trimmed = trimToNull(value)\n return trimmed ?? null\n}\n\nfunction shouldEmitLockContentionEvent(input: {\n tenantId: string\n organizationId?: string | null\n resourceKind: string\n resourceId: string\n lockedByUserId: string\n attemptedByUserId: string\n}): boolean {\n if (process.env.NODE_ENV === 'test') return true\n const now = Date.now()\n const key = [\n input.tenantId,\n normalizeScopeOrganization(input.organizationId) ?? 'global',\n input.resourceKind,\n input.resourceId,\n input.lockedByUserId,\n input.attemptedByUserId,\n ].join('|')\n\n const lastEmittedAt = lockContentionEventThrottle.get(key)\n if (typeof lastEmittedAt === 'number' && now - lastEmittedAt < LOCK_CONTENTION_EVENT_TTL_MS) {\n return false\n }\n\n lockContentionEventThrottle.set(key, now)\n\n for (const [cachedKey, cachedAt] of lockContentionEventThrottle.entries()) {\n if (now - cachedAt > LOCK_CONTENTION_EVENT_TTL_MS) {\n lockContentionEventThrottle.delete(cachedKey)\n }\n }\n if (lockContentionEventThrottle.size > LOCK_CONTENTION_EVENT_MAX_ENTRIES) {\n const oldest = Array.from(lockContentionEventThrottle.entries())\n .sort((left, right) => left[1] - right[1])\n .slice(0, lockContentionEventThrottle.size - LOCK_CONTENTION_EVENT_MAX_ENTRIES)\n for (const [staleKey] of oldest) lockContentionEventThrottle.delete(staleKey)\n }\n\n return true\n}\n\nfunction isActiveLockScopeUniqueViolation(error: unknown): boolean {\n if (error instanceof UniqueConstraintViolationException) {\n const errorWithConstraint = error as unknown as { constraint?: unknown }\n const constraint = typeof errorWithConstraint.constraint === 'string'\n ? errorWithConstraint.constraint\n : null\n if (constraint && ACTIVE_SCOPE_UNIQUE_CONSTRAINTS.has(constraint)) return true\n }\n if (!error || typeof error !== 'object') return false\n const code = (error as { code?: unknown }).code\n if (code !== '23505') return false\n const message = typeof (error as { message?: unknown }).message === 'string'\n ? (error as { message: string }).message.toLowerCase()\n : ''\n for (const constraint of ACTIVE_SCOPE_UNIQUE_CONSTRAINTS) {\n if (message.includes(constraint)) return true\n }\n return false\n}\n\nfunction getKysely(em: EntityManager): Kysely<any> {\n return (em as unknown as { getKysely: () => Kysely<any> }).getKysely()\n}\n\nconst SKIPPED_CONFLICT_FIELDS = new Set([\n 'updatedAt',\n 'updated_at',\n 'createdAt',\n 'created_at',\n 'deletedAt',\n 'deleted_at',\n])\n\nfunction shouldSkipConflictField(path: string): boolean {\n if (!path.trim().length) return true\n if (SKIPPED_CONFLICT_FIELDS.has(path)) return true\n const segments = path.split('.').filter((segment) => segment.length > 0)\n if (!segments.length) return true\n return SKIPPED_CONFLICT_FIELDS.has(segments[segments.length - 1] ?? '')\n}\n\nconst MISSING_CONFLICT_VALUE = Symbol('record_lock_conflict_missing_value')\n\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n return Boolean(value && typeof value === 'object' && !Array.isArray(value))\n}\n\nfunction parseDecryptedJsonLike(value: unknown): unknown {\n return typeof value === 'string' ? parseDecryptedFieldValue(value) : value\n}\n\nfunction readJsonRecordValue(value: unknown): Record<string, unknown> | null {\n const parsed = parseDecryptedJsonLike(value)\n return isRecordValue(parsed) ? parsed : null\n}\n\nfunction readActionLogChangesJson(log: ActionLog | null): Record<string, unknown> | null {\n return readJsonRecordValue(log?.changesJson ?? null)\n}\n\nfunction normalizeActionLogPayload(log: ActionLog | null): ActionLog | null {\n if (!log) return null\n log.changesJson = readJsonRecordValue(log.changesJson)\n log.snapshotBefore = parseDecryptedJsonLike(log.snapshotBefore)\n log.snapshotAfter = parseDecryptedJsonLike(log.snapshotAfter)\n log.contextJson = readJsonRecordValue(log.contextJson)\n log.commandPayload = parseDecryptedJsonLike(log.commandPayload)\n return log\n}\n\nfunction toIsoDate(value: unknown): string | null {\n if (value instanceof Date) {\n if (Number.isNaN(value.getTime())) return null\n return value.toISOString()\n }\n if (typeof value === 'string') {\n const parsed = new Date(value)\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString()\n }\n return null\n}\n\nfunction valuesEqual(a: unknown, b: unknown, seen?: Set<unknown>): boolean {\n if (Object.is(a, b)) return true\n\n if (a instanceof Date || b instanceof Date) {\n const left = toIsoDate(a)\n const right = toIsoDate(b)\n return left !== null && right !== null && left === right\n }\n\n if (Array.isArray(a) && Array.isArray(b)) {\n if (a.length !== b.length) return false\n for (let index = 0; index < a.length; index += 1) {\n if (!valuesEqual(a[index], b[index], seen)) return false\n }\n return true\n }\n\n if (isRecordValue(a) && isRecordValue(b)) {\n if (!seen) seen = new Set()\n if (seen.has(a) || seen.has(b)) return false\n seen.add(a)\n seen.add(b)\n const aKeys = Object.keys(a)\n const bKeys = Object.keys(b)\n if (aKeys.length !== bKeys.length) return false\n for (const key of aKeys) {\n if (!Object.prototype.hasOwnProperty.call(b, key)) return false\n if (!valuesEqual(a[key], b[key], seen)) return false\n }\n return true\n }\n\n return false\n}\n\nfunction readPathValue(source: unknown, path: string): unknown | typeof MISSING_CONFLICT_VALUE {\n if (!path.trim().length || !isRecordValue(source)) return MISSING_CONFLICT_VALUE\n if (Object.prototype.hasOwnProperty.call(source, path)) return source[path]\n\n const segments = path.split('.').filter((segment) => segment.length > 0)\n if (!segments.length) return MISSING_CONFLICT_VALUE\n\n let current: unknown = source\n for (const segment of segments) {\n if (!isRecordValue(current)) return MISSING_CONFLICT_VALUE\n if (!Object.prototype.hasOwnProperty.call(current, segment)) return MISSING_CONFLICT_VALUE\n current = current[segment]\n }\n return current\n}\n\nfunction buildPathVariants(path: string): string[] {\n const trimmed = path.trim()\n if (!trimmed.length) return []\n\n const segments = trimmed.split('.').filter((segment) => segment.length > 0)\n if (segments.length <= 1) return [trimmed]\n\n const variants = new Set<string>([trimmed])\n for (let index = 1; index < segments.length; index += 1) {\n variants.add(segments.slice(index).join('.'))\n }\n return Array.from(variants)\n}\n\nfunction readPathValueLoose(source: unknown, path: string): unknown | typeof MISSING_CONFLICT_VALUE {\n const variants = buildPathVariants(path)\n for (const variant of variants) {\n const value = readPathValue(source, variant)\n if (value !== MISSING_CONFLICT_VALUE) return value\n }\n return MISSING_CONFLICT_VALUE\n}\n\nfunction normalizeConflictValue(value: unknown): unknown {\n return value === undefined ? null : value\n}\n\nfunction formatNotificationValue(value: unknown): string {\n if (value === null || value === undefined) return '-'\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (value instanceof Date) return value.toISOString()\n try {\n return JSON.stringify(value)\n } catch {\n return String(value)\n }\n}\n\nfunction formatChangedFieldLabel(rawField: string): string {\n const trimmedField = rawField.trim()\n const withoutNamespace = trimmedField.includes('::') ? (trimmedField.split('::').pop() ?? trimmedField) : trimmedField\n const withoutPrefix = withoutNamespace.includes('.') ? (withoutNamespace.split('.').pop() ?? withoutNamespace) : withoutNamespace\n const words = withoutPrefix\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[._-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .split(' ')\n .filter(Boolean)\n\n if (!words.length) return trimmedField\n return words\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\nexport function readRecordLockHeaders(headers: Headers): ParsedRecordLockHeaders {\n const raw = {\n resourceKind: trimToNull(headers.get('x-om-record-lock-kind')) ?? undefined,\n resourceId: trimToNull(headers.get('x-om-record-lock-resource-id')) ?? undefined,\n token: trimToNull(headers.get('x-om-record-lock-token')) ?? undefined,\n baseLogId: trimToNull(headers.get('x-om-record-lock-base-log-id')) ?? undefined,\n resolution: trimToNull(headers.get('x-om-record-lock-resolution')) ?? undefined,\n conflictId: trimToNull(headers.get('x-om-record-lock-conflict-id')) ?? undefined,\n }\n\n const parsed = recordLockMutationHeaderSchema.partial().safeParse(raw)\n if (!parsed.success) return {}\n return parsed.data\n}\n\nexport class RecordLockService {\n private readonly em: EntityManager\n\n private readonly moduleConfigService: ModuleConfigService | null\n\n private readonly actionLogService: ActionLogService | null\n\n private readonly rbacService: RbacService | null\n\n constructor(deps: RecordLockServiceDeps) {\n this.em = deps.em\n this.moduleConfigService = deps.moduleConfigService ?? null\n this.actionLogService = deps.actionLogService ?? null\n this.rbacService = deps.rbacService ?? null\n }\n\n async getSettings(): Promise<RecordLockSettings> {\n if (!this.moduleConfigService) return DEFAULT_RECORD_LOCK_SETTINGS\n\n const value = await this.moduleConfigService.getValue<RecordLockSettings>(\n RECORD_LOCKS_MODULE_ID,\n RECORD_LOCKS_SETTINGS_NAME,\n { defaultValue: DEFAULT_RECORD_LOCK_SETTINGS },\n )\n\n return normalizeRecordLockSettings(value ?? DEFAULT_RECORD_LOCK_SETTINGS)\n }\n\n async saveSettings(input: RecordLockSettingsInput): Promise<RecordLockSettings> {\n const settings = normalizeRecordLockSettings(input)\n if (!this.moduleConfigService) return settings\n\n await this.moduleConfigService.setValue(RECORD_LOCKS_MODULE_ID, RECORD_LOCKS_SETTINGS_NAME, settings)\n return settings // NOSONAR \u2014 both paths return settings by design; the branch controls persistence\n }\n\n async acquire(input: RecordLockAcquireInput): Promise<RecordLockAcquireResult | RecordLockAcquireFailure> {\n this.scheduleCleanup(input.tenantId)\n const settings = await this.getSettings()\n const latest = await this.findLatestActionLogWithScopeFallback(input)\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n\n if (!resourceEnabled) {\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: false,\n strategy: settings.strategy,\n allowForceUnlock: settings.allowForceUnlock,\n heartbeatSeconds: settings.heartbeatSeconds,\n acquired: false,\n latestActionLogId: latest?.id ?? null,\n lock: null,\n }\n }\n\n const now = new Date()\n let activeLocks = await this.findActiveLocks(input, now)\n const ownedActiveLock = activeLocks.find((lock) => lock.lockedByUserId === input.userId) ?? null\n const competingActiveLock = activeLocks.find((lock) => lock.lockedByUserId !== input.userId) ?? null\n\n if (settings.strategy === 'pessimistic' && !ownedActiveLock && competingActiveLock) {\n const lockView = this.toLockView(competingActiveLock, false, activeLocks)\n if (shouldEmitLockContentionEvent({\n tenantId: competingActiveLock.tenantId,\n organizationId: competingActiveLock.organizationId,\n resourceKind: competingActiveLock.resourceKind,\n resourceId: competingActiveLock.resourceId,\n lockedByUserId: competingActiveLock.lockedByUserId,\n attemptedByUserId: input.userId,\n })) {\n await emitRecordLocksEvent('record_locks.lock.contended', {\n lockId: competingActiveLock.id,\n resourceKind: competingActiveLock.resourceKind,\n resourceId: competingActiveLock.resourceId,\n tenantId: competingActiveLock.tenantId,\n organizationId: competingActiveLock.organizationId,\n lockedByUserId: competingActiveLock.lockedByUserId,\n attemptedByUserId: input.userId,\n })\n }\n return {\n ok: false,\n status: 423,\n error: 'Record is currently locked by another user',\n code: 'record_locked',\n allowForceUnlock: settings.allowForceUnlock,\n lock: lockView,\n }\n }\n\n if (ownedActiveLock) {\n ownedActiveLock.strategy = settings.strategy\n ownedActiveLock.lockedByIp = input.lockedByIp ?? ownedActiveLock.lockedByIp ?? null\n ownedActiveLock.lastHeartbeatAt = now\n ownedActiveLock.expiresAt = new Date(now.getTime() + settings.timeoutSeconds * 1000)\n await this.em.flush()\n\n activeLocks = await this.findActiveLocks(input, now)\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n allowForceUnlock: settings.allowForceUnlock,\n heartbeatSeconds: settings.heartbeatSeconds,\n acquired: false,\n latestActionLogId: latest?.id ?? null,\n lock: this.toLockView(ownedActiveLock, true, activeLocks),\n }\n }\n\n const lock = this.em.create(RecordLock, {\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n token: randomUUID(),\n strategy: settings.strategy,\n status: ACTIVE_LOCK_STATUS,\n lockedByUserId: input.userId,\n lockedByIp: input.lockedByIp ?? null,\n baseActionLogId: latest?.id ?? null,\n lockedAt: now,\n lastHeartbeatAt: now,\n expiresAt: new Date(now.getTime() + settings.timeoutSeconds * 1000),\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n })\n\n this.em.persist(lock)\n let createdNewLock = true\n try {\n await this.em.flush()\n } catch (error) {\n if (!isActiveLockScopeUniqueViolation(error)) throw error\n const clear = (this.em as { clear?: () => void }).clear\n if (typeof clear === 'function') clear.call(this.em)\n const locksAfterCollision = await this.findActiveLocks(input, now)\n const competingAfterCollision = locksAfterCollision.find((item) => item.lockedByUserId !== input.userId) ?? null\n if (settings.strategy === 'pessimistic' && competingAfterCollision) {\n if (shouldEmitLockContentionEvent({\n tenantId: competingAfterCollision.tenantId,\n organizationId: competingAfterCollision.organizationId,\n resourceKind: competingAfterCollision.resourceKind,\n resourceId: competingAfterCollision.resourceId,\n lockedByUserId: competingAfterCollision.lockedByUserId,\n attemptedByUserId: input.userId,\n })) {\n await emitRecordLocksEvent('record_locks.lock.contended', {\n lockId: competingAfterCollision.id,\n resourceKind: competingAfterCollision.resourceKind,\n resourceId: competingAfterCollision.resourceId,\n tenantId: competingAfterCollision.tenantId,\n organizationId: competingAfterCollision.organizationId,\n lockedByUserId: competingAfterCollision.lockedByUserId,\n attemptedByUserId: input.userId,\n })\n }\n return {\n ok: false,\n status: 423,\n error: 'Record is currently locked by another user',\n code: 'record_locked',\n allowForceUnlock: settings.allowForceUnlock,\n lock: this.toLockView(competingAfterCollision, false, locksAfterCollision),\n }\n }\n const existingOwned = await this.findOwnedActiveLock(input)\n if (!existingOwned) throw error\n existingOwned.strategy = settings.strategy\n existingOwned.lockedByIp = input.lockedByIp ?? existingOwned.lockedByIp ?? null\n existingOwned.lastHeartbeatAt = now\n existingOwned.expiresAt = new Date(now.getTime() + settings.timeoutSeconds * 1000)\n await this.em.flush()\n createdNewLock = false\n }\n\n const activeAfterAcquire = await this.findActiveLocks(input, now)\n const ownedAfterAcquire = activeAfterAcquire.find((item) => item.lockedByUserId === input.userId)\n ?? await this.findOwnedActiveLock(input)\n ?? lock\n ?? null\n if (!ownedAfterAcquire) {\n const fallbackLock = activeAfterAcquire[0] ?? null\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n allowForceUnlock: settings.allowForceUnlock,\n heartbeatSeconds: settings.heartbeatSeconds,\n acquired: false,\n latestActionLogId: latest?.id ?? null,\n lock: fallbackLock ? this.toLockView(fallbackLock, false, activeAfterAcquire) : null,\n }\n }\n\n if (createdNewLock) {\n await emitRecordLocksEvent('record_locks.lock.acquired', {\n lockId: ownedAfterAcquire.id,\n resourceKind: ownedAfterAcquire.resourceKind,\n resourceId: ownedAfterAcquire.resourceId,\n tenantId: ownedAfterAcquire.tenantId,\n organizationId: ownedAfterAcquire.organizationId,\n lockedByUserId: ownedAfterAcquire.lockedByUserId,\n strategy: ownedAfterAcquire.strategy,\n baseActionLogId: ownedAfterAcquire.baseActionLogId,\n activeParticipantCount: activeAfterAcquire.length,\n })\n\n const recipientUserIds = activeAfterAcquire\n .filter((item) => item.lockedByUserId !== input.userId)\n .map((item) => item.lockedByUserId)\n const shouldSuppressJoinNotification = await this.hasRecentSavedRelease({\n tenantId: input.tenantId,\n organizationId: input.organizationId,\n userId: input.userId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n now,\n })\n\n if (!shouldSuppressJoinNotification) {\n await emitRecordLocksEvent('record_locks.participant.joined', {\n lockId: ownedAfterAcquire.id,\n resourceKind: ownedAfterAcquire.resourceKind,\n resourceId: ownedAfterAcquire.resourceId,\n tenantId: ownedAfterAcquire.tenantId,\n organizationId: ownedAfterAcquire.organizationId,\n joinedUserId: input.userId,\n joinedIp: ownedAfterAcquire.lockedByIp ?? null,\n recipientUserIds,\n activeParticipantCount: activeAfterAcquire.length,\n })\n }\n }\n\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n allowForceUnlock: settings.allowForceUnlock,\n heartbeatSeconds: settings.heartbeatSeconds,\n acquired: createdNewLock,\n latestActionLogId: latest?.id ?? null,\n lock: this.toLockView(ownedAfterAcquire, true, activeAfterAcquire),\n }\n }\n\n async heartbeat(input: RecordLockHeartbeatInput): Promise<RecordLockHeartbeatResult> {\n const settings = await this.getSettings()\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n if (!resourceEnabled) return { ok: true, expiresAt: null }\n\n const lock = await this.findOwnedLockByToken(input)\n if (!lock) return { ok: true, expiresAt: null }\n\n const now = new Date()\n if (lock.expiresAt <= now) {\n this.markLockReleased(lock, {\n status: 'expired',\n reason: 'expired',\n releasedByUserId: lock.lockedByUserId,\n now,\n })\n await this.em.flush()\n return { ok: true, expiresAt: null }\n }\n\n lock.lastHeartbeatAt = now\n lock.expiresAt = new Date(now.getTime() + settings.timeoutSeconds * 1000)\n await this.em.flush()\n return { ok: true, expiresAt: normalizeDate(lock.expiresAt) }\n }\n\n async release(input: RecordLockReleaseInput): Promise<RecordLockReleaseResult> {\n const settings = await this.getSettings()\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n if (!resourceEnabled) return { ok: true, released: false, conflictResolved: false }\n\n let conflictResolved = false\n if (input.reason === 'conflict_resolved' && input.conflictId && input.resolution === 'accept_incoming') {\n const conflict = await this.findConflictById(input.conflictId, input)\n if (conflict && conflict.status === 'pending' && conflict.conflictActorUserId === input.userId) {\n await this.resolveConflict(conflict, input.resolution, input.userId)\n conflictResolved = true\n }\n }\n\n const lock = input.token\n ? await this.findOwnedLockByToken(input)\n : await this.findOwnedActiveLock(input)\n if (!lock) return { ok: true, released: false, conflictResolved }\n\n const now = new Date()\n this.markLockReleased(lock, {\n status: 'released',\n reason: input.reason ?? 'cancelled',\n releasedByUserId: input.userId,\n now,\n })\n await this.em.flush()\n\n await emitRecordLocksEvent('record_locks.lock.released', {\n lockId: lock.id,\n resourceKind: lock.resourceKind,\n resourceId: lock.resourceId,\n tenantId: lock.tenantId,\n organizationId: lock.organizationId,\n lockedByUserId: lock.lockedByUserId,\n releasedByUserId: input.userId,\n reason: lock.releaseReason,\n })\n\n if (lock.releaseReason === 'unmount') {\n const remainingActiveLocks = await this.findActiveLocks(input, now)\n const recipientUserIds = remainingActiveLocks\n .map((activeLock) => activeLock.lockedByUserId)\n .filter((userId) => userId !== lock.lockedByUserId)\n\n if (recipientUserIds.length) {\n await emitRecordLocksEvent('record_locks.participant.left', {\n lockId: lock.id,\n resourceKind: lock.resourceKind,\n resourceId: lock.resourceId,\n tenantId: lock.tenantId,\n organizationId: lock.organizationId,\n leftUserId: lock.lockedByUserId,\n reason: 'unmount',\n recipientUserIds,\n activeParticipantCount: remainingActiveLocks.length,\n })\n }\n }\n\n return { ok: true, released: true, conflictResolved }\n }\n\n async forceRelease(input: RecordLockForceReleaseInput): Promise<RecordLockForceReleaseResult> {\n const settings = await this.getSettings()\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n const canForceRelease = await this.canUserForceRelease(input, settings)\n if (!resourceEnabled || !settings.allowForceUnlock || !canForceRelease) {\n return { ok: true, released: false, lock: null }\n }\n\n const now = new Date()\n const activeLocks = this.sortLocksByJoinOrder(await this.findActiveLocks(input, now))\n const lock = activeLocks[0] ?? null\n if (!lock) return { ok: true, released: false, lock: null }\n\n this.markLockReleased(lock, {\n status: 'force_released',\n reason: 'force',\n releasedByUserId: input.userId,\n now,\n })\n await this.em.flush()\n\n await emitRecordLocksEvent('record_locks.lock.force_released', {\n lockId: lock.id,\n resourceKind: lock.resourceKind,\n resourceId: lock.resourceId,\n tenantId: lock.tenantId,\n organizationId: lock.organizationId,\n lockedByUserId: lock.lockedByUserId,\n releasedByUserId: input.userId,\n reason: input.reason ?? null,\n })\n\n const remainingLocks = this.sortLocksByJoinOrder(activeLocks.filter((item) => item.id !== lock.id))\n const nextInQueue = remainingLocks[0] ?? null\n return { ok: true, released: true, lock: nextInQueue ? this.toLockView(nextInQueue, false, remainingLocks) : null }\n }\n\n async validateMutation(input: RecordLockMutationValidationInput): Promise<RecordLockValidationResult> {\n this.scheduleCleanup(input.tenantId)\n const settings = await this.getSettings()\n const resourceEnabled = isRecordLockingEnabledForResource(settings, input.resourceKind)\n const canOverrideIncoming = await this.canUserOverrideIncoming(input, settings)\n\n if (!resourceEnabled) {\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: false,\n strategy: settings.strategy,\n shouldReleaseOnSuccess: false,\n lock: null,\n latestActionLogId: null,\n }\n }\n\n const parsedHeaders = this.normalizeMutationHeaders(input.headers)\n const keepMineResolution = parsedHeaders.resolution === 'accept_mine' || parsedHeaders.resolution === 'merged'\n ? parsedHeaders.resolution\n : null\n const hasKeepMineIntent = keepMineResolution !== null\n const now = new Date()\n const activeLocks = await this.findActiveLocks(input, now)\n const ownedActiveLock = activeLocks.find((lock) => lock.lockedByUserId === input.userId) ?? null\n const competingLock = activeLocks.find((lock) => lock.lockedByUserId !== input.userId) ?? null\n const latest = await this.findLatestActionLogWithScopeFallback(input)\n const shouldReleaseOnSuccess = Boolean(\n ownedActiveLock\n && (!parsedHeaders.token || ownedActiveLock.token === parsedHeaders.token),\n )\n\n if (settings.strategy === 'pessimistic') {\n if (competingLock && !ownedActiveLock) {\n return {\n ok: false,\n status: 423,\n error: 'Record is currently locked by another user',\n code: 'record_locked',\n lock: this.toLockView(competingLock, false, activeLocks),\n }\n }\n\n if (ownedActiveLock) {\n if (parsedHeaders.token && ownedActiveLock.token !== parsedHeaders.token) {\n return {\n ok: false,\n status: 423,\n error: 'Valid lock token is required for this mutation',\n code: 'record_locked',\n lock: this.toLockView(ownedActiveLock, false, activeLocks),\n }\n }\n }\n\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n shouldReleaseOnSuccess,\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n latestActionLogId: latest?.id ?? null,\n }\n }\n\n const existingConflict = parsedHeaders.conflictId\n ? await this.findConflictById(parsedHeaders.conflictId, input)\n : null\n\n if (existingConflict) {\n const canResolveExistingConflict = existingConflict.status === 'pending'\n && existingConflict.conflictActorUserId === input.userId\n\n if (parsedHeaders.resolution === 'accept_mine' || parsedHeaders.resolution === 'merged') {\n const isAlreadyResolvedByRequester = existingConflict.conflictActorUserId === input.userId\n && existingConflict.status !== 'pending'\n && existingConflict.resolution === parsedHeaders.resolution\n\n if (!canResolveExistingConflict && !isAlreadyResolvedByRequester) {\n return {\n ok: false,\n status: 409,\n error: 'Record conflict requires resolution before saving',\n code: 'record_lock_conflict',\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n conflict: await this.toConflictPayload(existingConflict, input.mutationPayload ?? null, settings.allowIncomingOverride, canOverrideIncoming),\n }\n }\n if (!canOverrideIncoming) {\n return {\n ok: false,\n status: 409,\n error: 'Record conflict requires resolution before saving',\n code: 'record_lock_conflict',\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n conflict: await this.toConflictPayload(existingConflict, input.mutationPayload ?? null, settings.allowIncomingOverride, canOverrideIncoming),\n }\n }\n if (canResolveExistingConflict) {\n await this.resolveConflict(existingConflict, parsedHeaders.resolution, input.userId)\n }\n } else {\n return {\n ok: false,\n status: 409,\n error: 'Record conflict requires resolution before saving',\n code: 'record_lock_conflict',\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n conflict: await this.toConflictPayload(existingConflict, input.mutationPayload ?? null, settings.allowIncomingOverride, canOverrideIncoming),\n }\n }\n }\n\n if (!existingConflict) {\n const baseActionLogId = parsedHeaders.baseLogId\n ?? (ownedActiveLock ? ownedActiveLock.baseActionLogId : null)\n\n const hasConflictingBaseLog = Boolean(\n latest?.id\n && baseActionLogId\n && latest.id !== baseActionLogId\n )\n const hasConflictingWriteAfterLockStart = Boolean(\n latest?.id\n && !baseActionLogId\n && ownedActiveLock\n && latest.createdAt instanceof Date\n && ownedActiveLock.lockedAt instanceof Date\n && latest.createdAt.getTime() > ownedActiveLock.lockedAt.getTime()\n && latest.actorUserId !== input.userId\n )\n const isConflictingWrite = hasConflictingBaseLog || hasConflictingWriteAfterLockStart\n\n if (isConflictingWrite) {\n if (keepMineResolution && canOverrideIncoming) {\n const autoResolvedConflict = await this.createConflict({\n scope: input,\n baseActionLogId,\n incomingActionLogId: latest?.id ?? null,\n conflictActorUserId: input.userId,\n incomingActorUserId: latest?.actorUserId ?? null,\n })\n await this.resolveConflict(autoResolvedConflict, keepMineResolution, input.userId)\n\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n shouldReleaseOnSuccess,\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n latestActionLogId: latest?.id ?? null,\n }\n }\n\n const conflict = await this.createConflict({\n scope: input,\n baseActionLogId,\n incomingActionLogId: latest?.id ?? null,\n conflictActorUserId: input.userId,\n incomingActorUserId: latest?.actorUserId ?? null,\n })\n\n return {\n ok: false,\n status: 409,\n error: 'Record conflict detected',\n code: 'record_lock_conflict',\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n conflict: await this.toConflictPayload(conflict, input.mutationPayload ?? null, settings.allowIncomingOverride, canOverrideIncoming),\n }\n }\n }\n\n return {\n ok: true,\n enabled: settings.enabled,\n resourceEnabled: true,\n strategy: settings.strategy,\n shouldReleaseOnSuccess,\n lock: ownedActiveLock ? this.toLockView(ownedActiveLock, false, activeLocks) : null,\n latestActionLogId: latest?.id ?? null,\n }\n }\n\n async releaseAfterMutation(input: RecordLockReleaseInput): Promise<void> {\n const releaseResult = await this.release({\n ...input,\n reason: input.reason ?? 'saved',\n })\n if (!releaseResult.released) return\n }\n\n async emitIncomingChangesNotificationAfterMutation(input: {\n tenantId: string\n organizationId?: string | null\n userId: string\n resourceKind: string\n resourceId: string\n method: 'PUT' | 'DELETE'\n }): Promise<void> {\n if (input.method !== 'PUT') return\n const settings = await this.getSettings()\n if (!settings.notifyOnConflict || !isRecordLockingEnabledForResource(settings, input.resourceKind)) return\n\n const now = new Date()\n let activeLocks = await this.findActiveLocks(input, now)\n if (!activeLocks.length) {\n const fallbackWhere: FilterQuery<RecordLock> = {\n tenantId: input.tenantId,\n deletedAt: null,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n status: ACTIVE_LOCK_STATUS,\n }\n const fallbackLocks = await this.em.find(RecordLock, fallbackWhere, { orderBy: { updatedAt: 'desc' } })\n activeLocks = Array.isArray(fallbackLocks) ? fallbackLocks : []\n }\n\n const recipientUserIds = new Set<string>()\n for (const lock of activeLocks) {\n if (lock.lockedByUserId !== input.userId) {\n recipientUserIds.add(lock.lockedByUserId)\n }\n }\n if (!recipientUserIds.size) {\n const fallbackWindowMs = Math.max((settings.timeoutSeconds ?? 300) * 1000, 60_000)\n const fallbackSince = new Date(now.getTime() - fallbackWindowMs)\n const recentLocks = await this.em.find(RecordLock, {\n tenantId: input.tenantId,\n deletedAt: null,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n updatedAt: { $gte: fallbackSince },\n }, { orderBy: { updatedAt: 'desc' }, limit: 50 })\n\n for (const lock of (Array.isArray(recentLocks) ? recentLocks : [])) {\n if (lock.lockedByUserId !== input.userId) {\n recipientUserIds.add(lock.lockedByUserId)\n }\n }\n }\n if (!recipientUserIds.size) return\n\n let latest = await this.findLatestActionLog(input)\n if (!latest) {\n latest = await this.findLatestActionLog({\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n })\n }\n let actorLog = latest?.actorUserId === input.userId\n ? latest\n : await this.findLatestActionLogByActor(input, input.userId)\n if (!actorLog) {\n actorLog = await this.findLatestActionLogByActor({\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n }, input.userId)\n }\n const incomingLog = actorLog ?? latest\n\n const changedFields = incomingLog\n ? this.summarizeChangedFieldsFromActionLog(incomingLog)\n : ''\n const changedRows = this.buildIncomingChangeRowsFromActionLog(incomingLog)\n const changedRowsJson = changedRows.length ? JSON.stringify(changedRows) : ''\n\n await emitRecordLocksEvent('record_locks.incoming_changes.available', {\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n incomingActorUserId: input.userId,\n incomingActionLogId: incomingLog?.id ?? null,\n recipientUserIds: Array.from(recipientUserIds),\n changedFields: changedFields || '-',\n changedRowsJson,\n })\n }\n\n async emitRecordDeletedNotificationAfterMutation(input: {\n tenantId: string\n organizationId?: string | null\n userId: string\n resourceKind: string\n resourceId: string\n method: 'PUT' | 'DELETE'\n }): Promise<void> {\n if (input.method !== 'DELETE') return\n const settings = await this.getSettings()\n if (!isRecordLockingEnabledForResource(settings, input.resourceKind)) return\n\n const now = new Date()\n let activeLocks = await this.findActiveLocks(input, now)\n if (!activeLocks.length) {\n const fallbackWhere: FilterQuery<RecordLock> = {\n tenantId: input.tenantId,\n deletedAt: null,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n status: ACTIVE_LOCK_STATUS,\n }\n const fallbackLocks = await this.em.find(RecordLock, fallbackWhere, { orderBy: { updatedAt: 'desc' } })\n activeLocks = Array.isArray(fallbackLocks) ? fallbackLocks : []\n }\n\n const recipientUserIds = new Set<string>()\n for (const lock of activeLocks) {\n if (lock.lockedByUserId !== input.userId) {\n recipientUserIds.add(lock.lockedByUserId)\n }\n }\n\n if (!recipientUserIds.size) {\n const fallbackWindowMs = Math.max((settings.timeoutSeconds ?? 300) * 1000, 60_000)\n const fallbackSince = new Date(now.getTime() - fallbackWindowMs)\n const recentLocks = await this.em.find(RecordLock, {\n tenantId: input.tenantId,\n deletedAt: null,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n updatedAt: { $gte: fallbackSince },\n }, { orderBy: { updatedAt: 'desc' }, limit: 50 })\n\n for (const lock of (Array.isArray(recentLocks) ? recentLocks : [])) {\n if (lock.lockedByUserId !== input.userId) {\n recipientUserIds.add(lock.lockedByUserId)\n }\n }\n }\n if (!recipientUserIds.size) return\n\n await emitRecordLocksEvent('record_locks.record.deleted', {\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n deletedByUserId: input.userId,\n recipientUserIds: Array.from(recipientUserIds),\n })\n }\n\n async resolveConflictById(input: {\n conflictId: string\n tenantId: string\n organizationId?: string | null\n userId: string\n resolution: 'accept_incoming' | 'accept_mine' | 'merged'\n }): Promise<boolean> {\n const settings = await this.getSettings()\n const canOverrideIncoming = await this.canUserOverrideIncoming(input, settings)\n const conflict = await this.em.findOne(RecordLockConflict, {\n id: input.conflictId,\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n deletedAt: null,\n })\n if (!conflict || conflict.status !== 'pending' || conflict.conflictActorUserId !== input.userId) {\n return false\n }\n if ((input.resolution === 'accept_mine' || input.resolution === 'merged') && !canOverrideIncoming) {\n return false\n }\n await this.resolveConflict(conflict, input.resolution, input.userId)\n return true\n }\n\n private async canUserOverrideIncoming(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId' | 'userId'>,\n settings: RecordLockSettings,\n ): Promise<boolean> {\n if (!settings.allowIncomingOverride) return false\n if (!this.rbacService) return false\n\n try {\n return await this.rbacService.userHasAllFeatures(\n input.userId,\n ['record_locks.override_incoming'],\n {\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n },\n )\n } catch {\n return false\n }\n }\n\n private async canUserForceRelease(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId' | 'userId'>,\n settings: RecordLockSettings,\n ): Promise<boolean> {\n if (!settings.allowForceUnlock) return false\n if (!this.rbacService) return false\n\n try {\n return await this.rbacService.userHasAllFeatures(\n input.userId,\n ['record_locks.force_release'],\n {\n tenantId: input.tenantId,\n organizationId: normalizeScopeOrganization(input.organizationId),\n },\n )\n } catch {\n return false\n }\n }\n\n private pruneLockCleanupState(now: number): void {\n for (const [tenantId, state] of lockCleanupStateByTenant.entries()) {\n if (!state.inFlight && now - state.lastSeenAt > LOCK_CLEANUP_STATE_TTL_MS) {\n lockCleanupStateByTenant.delete(tenantId)\n }\n }\n\n if (lockCleanupStateByTenant.size <= LOCK_CLEANUP_STATE_MAX_ENTRIES) return\n\n const removable = Array.from(lockCleanupStateByTenant.entries())\n .filter(([, state]) => !state.inFlight)\n .sort((left, right) => left[1].lastSeenAt - right[1].lastSeenAt)\n const overflow = lockCleanupStateByTenant.size - LOCK_CLEANUP_STATE_MAX_ENTRIES\n for (const [tenantId] of removable.slice(0, Math.max(0, overflow))) {\n lockCleanupStateByTenant.delete(tenantId)\n }\n }\n\n private scheduleCleanup(tenantId: string): void {\n const now = Date.now()\n this.pruneLockCleanupState(now)\n const state = lockCleanupStateByTenant.get(tenantId) ?? { lastRunAt: 0, inFlight: false, lastSeenAt: now }\n state.lastSeenAt = now\n lockCleanupStateByTenant.set(tenantId, state)\n if (state.inFlight) return\n if (now - state.lastRunAt < LOCK_CLEANUP_INTERVAL_MS) return\n\n state.inFlight = true\n state.lastRunAt = now\n lockCleanupStateByTenant.set(tenantId, state)\n\n void this.cleanupHistoricalRecords(tenantId).finally(() => {\n const current = lockCleanupStateByTenant.get(tenantId)\n if (!current) return\n current.inFlight = false\n lockCleanupStateByTenant.set(tenantId, current)\n })\n }\n\n private async cleanupHistoricalRecords(tenantId: string): Promise<void> {\n try {\n const db = getKysely(this.em)\n const now = Date.now()\n const lockCutoff = new Date(now - LOCK_RETENTION_MS)\n const resolvedConflictCutoff = new Date(now - RESOLVED_CONFLICT_RETENTION_MS)\n const pendingConflictCutoff = new Date(now - PENDING_CONFLICT_RETENTION_MS)\n const deletedAt = new Date(now)\n\n await db\n .updateTable('record_locks' as any)\n .set({\n deleted_at: deletedAt,\n updated_at: deletedAt,\n } as any)\n .where('tenant_id' as any, '=', tenantId)\n .where('deleted_at' as any, 'is', null as any)\n .where('status' as any, '!=', ACTIVE_LOCK_STATUS)\n .where('updated_at' as any, '<', lockCutoff)\n .execute()\n\n await db\n .updateTable('record_lock_conflicts' as any)\n .set({\n deleted_at: deletedAt,\n updated_at: deletedAt,\n } as any)\n .where('tenant_id' as any, '=', tenantId)\n .where('deleted_at' as any, 'is', null as any)\n .where((eb: any) => eb.or([\n eb.and([\n eb('status' as any, '=', 'pending'),\n eb('created_at' as any, '<', pendingConflictCutoff),\n ]),\n eb.and([\n eb('status' as any, '!=', 'pending'),\n eb('updated_at' as any, '<', resolvedConflictCutoff),\n ]),\n ]))\n .execute()\n } catch {\n // Best-effort cleanup must never fail lock workflows.\n }\n }\n\n private normalizeMutationHeaders(headers: Partial<RecordLockMutationHeaders>): Partial<RecordLockMutationHeaders> {\n const parsed = recordLockMutationHeaderSchema.partial().safeParse(headers)\n if (!parsed.success) return {}\n return parsed.data\n }\n\n private buildScopeWhere(scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'>): {\n tenantId: string\n deletedAt: null\n organizationId?: string | null\n } {\n const where: {\n tenantId: string\n deletedAt: null\n organizationId?: string | null\n } = {\n tenantId: scope.tenantId,\n deletedAt: null,\n }\n\n if (scope.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(scope.organizationId)\n }\n\n return where\n }\n\n private async findActiveLocks(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n now: Date,\n ): Promise<RecordLock[]> {\n const legacyFinder = (this as unknown as {\n findActiveLock?: (args: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource, at: Date) => Promise<RecordLock | null>\n }).findActiveLock\n if (typeof legacyFinder === 'function') {\n const legacyResult = await legacyFinder(input, now)\n return legacyResult ? [legacyResult] : []\n }\n\n const where: FilterQuery<RecordLock> = {\n ...this.buildScopeWhere(input),\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n status: ACTIVE_LOCK_STATUS,\n }\n\n const locks = await this.em.find(RecordLock, where, { orderBy: { updatedAt: 'desc' } })\n if (!Array.isArray(locks) || !locks.length) return []\n\n let dirty = false\n const active: RecordLock[] = []\n const expiredLocks: RecordLock[] = []\n\n for (const lock of locks) {\n if (lock.expiresAt <= now) {\n this.markLockReleased(lock, {\n status: 'expired',\n reason: 'expired',\n releasedByUserId: lock.lockedByUserId,\n now,\n })\n dirty = true\n expiredLocks.push(lock)\n continue\n }\n\n active.push(lock)\n }\n\n if (dirty) await this.em.flush()\n if (expiredLocks.length) {\n const recipientUserIds = active.map((lock) => lock.lockedByUserId)\n for (const expiredLock of expiredLocks) {\n await emitRecordLocksEvent('record_locks.participant.left', {\n lockId: expiredLock.id,\n resourceKind: expiredLock.resourceKind,\n resourceId: expiredLock.resourceId,\n tenantId: expiredLock.tenantId,\n organizationId: expiredLock.organizationId,\n leftUserId: expiredLock.lockedByUserId,\n reason: 'expired',\n recipientUserIds,\n activeParticipantCount: active.length,\n })\n }\n }\n return active\n }\n\n private async findOwnedLockByToken(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId' | 'userId'> & RecordLockResource & { token?: string },\n ): Promise<RecordLock | null> {\n if (!input.token) return null\n\n const where: FilterQuery<RecordLock> = {\n ...this.buildScopeWhere(input),\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n token: input.token,\n lockedByUserId: input.userId,\n status: ACTIVE_LOCK_STATUS,\n }\n\n return this.em.findOne(RecordLock, where)\n }\n\n private async findOwnedActiveLock(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId' | 'userId'> & RecordLockResource,\n ): Promise<RecordLock | null> {\n const where: FilterQuery<RecordLock> = {\n ...this.buildScopeWhere(input),\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n lockedByUserId: input.userId,\n status: ACTIVE_LOCK_STATUS,\n }\n return this.em.findOne(RecordLock, where)\n }\n\n private async hasRecentSavedRelease(input: {\n tenantId: string\n organizationId?: string | null\n userId: string\n resourceKind: string\n resourceId: string\n now: Date\n }): Promise<boolean> {\n const cutoff = new Date(input.now.getTime() - PARTICIPANT_REJOIN_AFTER_SAVE_SUPPRESS_MS)\n const where: FilterQuery<RecordLock> = {\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n lockedByUserId: input.userId,\n status: 'released',\n releaseReason: 'saved',\n deletedAt: null,\n releasedAt: { $gte: cutoff },\n }\n if (input.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(input.organizationId)\n }\n\n const scoped = await this.em.findOne(RecordLock, where, { orderBy: { releasedAt: 'desc' } })\n if (scoped) return true\n if (input.organizationId === undefined) return false\n\n return Boolean(await this.em.findOne(RecordLock, {\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n lockedByUserId: input.userId,\n status: 'released',\n releaseReason: 'saved',\n deletedAt: null,\n releasedAt: { $gte: cutoff },\n }, { orderBy: { releasedAt: 'desc' } }))\n }\n\n private markLockReleased(\n lock: RecordLock,\n params: {\n status: RecordLockStatus\n reason: RecordLockReleaseReason\n releasedByUserId: string\n now: Date\n },\n ) {\n lock.status = params.status\n lock.releaseReason = params.reason\n lock.releasedByUserId = params.releasedByUserId\n lock.releasedAt = params.now\n lock.updatedAt = params.now\n }\n\n private async findLatestActionLog(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n ): Promise<ActionLog | null> {\n const where: FilterQuery<ActionLog> = {\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n deletedAt: null,\n }\n\n if (input.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(input.organizationId)\n }\n\n return normalizeActionLogPayload(await findOneWithDecryption(\n this.em,\n ActionLog,\n where,\n { orderBy: { createdAt: 'desc' } },\n { tenantId: input.tenantId, organizationId: normalizeScopeOrganization(input.organizationId) },\n ))\n }\n\n private async findLatestActionLogWithScopeFallback(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n ): Promise<ActionLog | null> {\n const scoped = await this.findLatestActionLog(input)\n if (scoped) return scoped\n if (input.organizationId !== null) return null\n\n return this.findLatestActionLog({\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n })\n }\n\n private async findLatestActionLogByActor(\n input: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n actorUserId: string,\n ): Promise<ActionLog | null> {\n const where: FilterQuery<ActionLog> = {\n tenantId: input.tenantId,\n resourceKind: input.resourceKind,\n resourceId: input.resourceId,\n actorUserId,\n deletedAt: null,\n }\n\n if (input.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(input.organizationId)\n }\n\n return normalizeActionLogPayload(await findOneWithDecryption(\n this.em,\n ActionLog,\n where,\n { orderBy: { createdAt: 'desc' } },\n { tenantId: input.tenantId, organizationId: normalizeScopeOrganization(input.organizationId) },\n ))\n }\n\n private summarizeChangedFieldsFromActionLog(log: ActionLog | null): string {\n if (!log) return ''\n\n const changesJson = readActionLogChangesJson(log)\n if (changesJson) {\n const fromChanges = Object.keys(changesJson)\n .filter((field) => !shouldSkipConflictField(field))\n .slice(0, 12)\n .map(formatChangedFieldLabel)\n .join(', ')\n if (fromChanges) return fromChanges\n }\n\n const before = readJsonRecordValue(log.snapshotBefore)\n const after = readJsonRecordValue(log.snapshotAfter)\n if (!before || !after) return ''\n\n const diffPaths = new Set<string>()\n this.collectSnapshotDiffPaths(before, after, null, diffPaths, new Set<unknown>())\n\n return Array.from(diffPaths)\n .filter((field) => !shouldSkipConflictField(field))\n .sort((left, right) => left.localeCompare(right))\n .slice(0, 12)\n .map(formatChangedFieldLabel)\n .join(', ')\n }\n\n private buildIncomingChangeRowsFromActionLog(log: ActionLog | null): Array<{\n field: string\n incoming: string\n current: string\n }> {\n const changesJson = readActionLogChangesJson(log)\n if (!changesJson) return []\n\n const rows: Array<{ field: string; incoming: string; current: string }> = []\n for (const [rawField, rawChange] of Object.entries(changesJson)) {\n if (rows.length >= 12) break\n if (shouldSkipConflictField(rawField)) continue\n\n const change = isRecordValue(rawChange) ? rawChange : {}\n const incoming = Object.prototype.hasOwnProperty.call(change, 'to')\n ? formatNotificationValue(change.to)\n : '-'\n const current = Object.prototype.hasOwnProperty.call(change, 'from')\n ? formatNotificationValue(change.from)\n : '-'\n\n rows.push({\n field: formatChangedFieldLabel(rawField),\n incoming,\n current,\n })\n }\n\n return rows\n }\n\n private toParticipantView(lock: RecordLock): RecordLockParticipantView {\n return {\n userId: lock.lockedByUserId,\n lockedByIp: lock.lockedByIp ?? null,\n lockedAt: normalizeDate(lock.lockedAt),\n lastHeartbeatAt: normalizeDate(lock.lastHeartbeatAt),\n expiresAt: normalizeDate(lock.expiresAt),\n }\n }\n\n private sortLocksByJoinOrder(locks: RecordLock[]): RecordLock[] {\n return [...locks].sort((left, right) => {\n const leftLockedAt = left.lockedAt instanceof Date ? left.lockedAt.getTime() : 0\n const rightLockedAt = right.lockedAt instanceof Date ? right.lockedAt.getTime() : 0\n if (leftLockedAt !== rightLockedAt) return leftLockedAt - rightLockedAt\n\n const leftCreatedAt = left.createdAt instanceof Date ? left.createdAt.getTime() : 0\n const rightCreatedAt = right.createdAt instanceof Date ? right.createdAt.getTime() : 0\n if (leftCreatedAt !== rightCreatedAt) return leftCreatedAt - rightCreatedAt\n\n return left.id.localeCompare(right.id)\n })\n }\n\n private toLockView(lock: RecordLock, includeToken: boolean, activeLocks: RecordLock[] = [lock]): RecordLockView {\n const participants = activeLocks.map((item) => this.toParticipantView(item))\n return {\n id: lock.id,\n resourceKind: lock.resourceKind,\n resourceId: lock.resourceId,\n token: includeToken ? lock.token : null,\n strategy: lock.strategy,\n status: lock.status,\n lockedByUserId: lock.lockedByUserId,\n lockedByIp: lock.lockedByIp ?? null,\n baseActionLogId: lock.baseActionLogId,\n lockedAt: normalizeDate(lock.lockedAt),\n lastHeartbeatAt: normalizeDate(lock.lastHeartbeatAt),\n expiresAt: normalizeDate(lock.expiresAt),\n participants,\n activeParticipantCount: participants.length,\n }\n }\n\n private async createConflict(input: {\n scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource\n baseActionLogId: string | null\n incomingActionLogId: string | null\n conflictActorUserId: string\n incomingActorUserId: string | null\n }): Promise<RecordLockConflict> {\n const dedupeKey = [\n 'record_locks',\n 'conflict',\n input.scope.tenantId,\n normalizeScopeOrganization(input.scope.organizationId) ?? 'global',\n input.scope.resourceKind,\n input.scope.resourceId,\n input.conflictActorUserId,\n input.baseActionLogId ?? 'none',\n input.incomingActionLogId ?? 'none',\n ].join(':')\n\n const result = await this.em.transactional(async (tx) => {\n try {\n const db = getKysely(tx as EntityManager)\n await sql`select pg_advisory_xact_lock(hashtext(${dedupeKey}))`.execute(db)\n } catch {\n // Best-effort lock; fallback to find-first behavior below.\n }\n\n const existing = await this.findPendingConflictByFingerprint(tx as EntityManager, input)\n if (existing) {\n return { conflict: existing, created: false as const }\n }\n\n const conflict = tx.create(RecordLockConflict, {\n resourceKind: input.scope.resourceKind,\n resourceId: input.scope.resourceId,\n status: 'pending',\n resolution: null,\n baseActionLogId: input.baseActionLogId,\n incomingActionLogId: input.incomingActionLogId,\n conflictActorUserId: input.conflictActorUserId,\n incomingActorUserId: input.incomingActorUserId,\n tenantId: input.scope.tenantId,\n organizationId: normalizeScopeOrganization(input.scope.organizationId),\n })\n\n tx.persist(conflict)\n await tx.flush()\n return { conflict, created: true as const }\n })\n\n if (result.created) {\n await emitRecordLocksEvent('record_locks.conflict.detected', {\n conflictId: result.conflict.id,\n resourceKind: result.conflict.resourceKind,\n resourceId: result.conflict.resourceId,\n tenantId: result.conflict.tenantId,\n organizationId: result.conflict.organizationId,\n conflictActorUserId: result.conflict.conflictActorUserId,\n incomingActorUserId: result.conflict.incomingActorUserId,\n baseActionLogId: result.conflict.baseActionLogId,\n incomingActionLogId: result.conflict.incomingActionLogId,\n })\n }\n\n return result.conflict\n }\n\n private findPendingConflictByFingerprint(\n em: EntityManager,\n input: {\n scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource\n baseActionLogId: string | null\n incomingActionLogId: string | null\n conflictActorUserId: string\n },\n ): Promise<RecordLockConflict | null> {\n return em.findOne(RecordLockConflict, {\n tenantId: input.scope.tenantId,\n organizationId: normalizeScopeOrganization(input.scope.organizationId),\n resourceKind: input.scope.resourceKind,\n resourceId: input.scope.resourceId,\n conflictActorUserId: input.conflictActorUserId,\n status: 'pending',\n baseActionLogId: input.baseActionLogId,\n incomingActionLogId: input.incomingActionLogId,\n deletedAt: null,\n }, { orderBy: { createdAt: 'desc' } })\n }\n\n private async resolveConflict(\n conflict: RecordLockConflict,\n resolution: 'accept_incoming' | Extract<RecordLockMutationHeaders['resolution'], 'accept_mine' | 'merged'>,\n resolvedByUserId: string,\n ): Promise<void> {\n const now = new Date()\n\n const resolutionMap: Record<'accept_incoming' | Extract<RecordLockMutationHeaders['resolution'], 'accept_mine' | 'merged'>, {\n status: RecordLockConflictStatus\n resolution: RecordLockConflictResolution\n }> = {\n accept_incoming: { status: 'resolved_accept_incoming', resolution: 'accept_incoming' },\n accept_mine: { status: 'resolved_accept_mine', resolution: 'accept_mine' },\n merged: { status: 'resolved_merged', resolution: 'merged' },\n }\n\n const target = resolutionMap[resolution]\n conflict.status = target.status\n conflict.resolution = target.resolution\n conflict.resolvedByUserId = resolvedByUserId\n conflict.resolvedAt = now\n conflict.updatedAt = now\n await this.em.flush()\n\n await emitRecordLocksEvent('record_locks.conflict.resolved', {\n conflictId: conflict.id,\n resourceKind: conflict.resourceKind,\n resourceId: conflict.resourceId,\n tenantId: conflict.tenantId,\n organizationId: conflict.organizationId,\n conflictActorUserId: conflict.conflictActorUserId,\n incomingActorUserId: conflict.incomingActorUserId,\n resolution: conflict.resolution,\n resolvedByUserId,\n })\n }\n\n private async findConflictById(\n conflictId: string,\n scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n ): Promise<RecordLockConflict | null> {\n const where: FilterQuery<RecordLockConflict> = {\n id: conflictId,\n tenantId: scope.tenantId,\n resourceKind: scope.resourceKind,\n resourceId: scope.resourceId,\n deletedAt: null,\n }\n\n if (scope.organizationId !== undefined) {\n where.organizationId = normalizeScopeOrganization(scope.organizationId)\n }\n\n const scoped = await this.em.findOne(RecordLockConflict, where)\n if (scoped || scope.organizationId === undefined) return scoped\n\n return this.em.findOne(RecordLockConflict, {\n id: conflictId,\n tenantId: scope.tenantId,\n resourceKind: scope.resourceKind,\n resourceId: scope.resourceId,\n deletedAt: null,\n })\n }\n\n private async findActionLogById(\n logId: string | null,\n scope: Pick<RecordLockScope, 'tenantId' | 'organizationId'> & RecordLockResource,\n ): Promise<ActionLog | null> {\n if (!logId) return null\n\n let resolved = this.actionLogService\n ? await this.actionLogService.findById(logId)\n : null\n if (!resolved) {\n resolved = await findOneWithDecryption(\n this.em,\n ActionLog,\n { id: logId, deletedAt: null },\n undefined,\n { tenantId: scope.tenantId, organizationId: normalizeScopeOrganization(scope.organizationId) },\n )\n }\n resolved = normalizeActionLogPayload(resolved)\n if (!resolved || resolved.deletedAt) return null\n\n if (resolved.tenantId !== scope.tenantId) return null\n\n if (scope.organizationId !== undefined) {\n const expectedOrganizationId = normalizeScopeOrganization(scope.organizationId)\n if (normalizeScopeOrganization(resolved.organizationId) !== expectedOrganizationId) return null\n }\n\n if (resolved.resourceKind !== scope.resourceKind || resolved.resourceId !== scope.resourceId) {\n return null\n }\n\n return resolved\n }\n\n private collectSnapshotDiffPaths(\n before: unknown,\n after: unknown,\n pathPrefix: string | null,\n output: Set<string>,\n seen: Set<unknown>,\n ): void {\n if (valuesEqual(before, after)) return\n\n const beforeRecord = isRecordValue(before) ? before : null\n const afterRecord = isRecordValue(after) ? after : null\n\n if (!beforeRecord || !afterRecord) {\n if (pathPrefix) output.add(pathPrefix)\n return\n }\n\n if (seen.has(beforeRecord) || seen.has(afterRecord)) {\n if (pathPrefix) output.add(pathPrefix)\n return\n }\n\n seen.add(beforeRecord)\n seen.add(afterRecord)\n\n const keys = new Set([...Object.keys(beforeRecord), ...Object.keys(afterRecord)])\n for (const key of keys) {\n if (SKIPPED_CONFLICT_FIELDS.has(key)) continue\n const nextPath = pathPrefix ? `${pathPrefix}.${key}` : key\n this.collectSnapshotDiffPaths(beforeRecord[key], afterRecord[key], nextPath, output, seen)\n }\n }\n\n private async buildConflictChanges(\n conflict: RecordLockConflict,\n mutationPayload: Record<string, unknown> | null,\n ): Promise<RecordLockConflictChange[]> {\n const scope = {\n tenantId: conflict.tenantId,\n organizationId: conflict.organizationId,\n resourceKind: conflict.resourceKind,\n resourceId: conflict.resourceId,\n }\n\n const baseLog = await this.findActionLogById(conflict.baseActionLogId, scope)\n const incomingLog = await this.findActionLogById(conflict.incomingActionLogId, scope)\n\n const baseSnapshot = readJsonRecordValue(baseLog?.snapshotAfter ?? null)\n const incomingBeforeSnapshot = readJsonRecordValue(incomingLog?.snapshotBefore ?? null)\n const incomingAfterSnapshot = readJsonRecordValue(incomingLog?.snapshotAfter ?? null)\n const fallbackBaseSnapshot = baseSnapshot ?? incomingBeforeSnapshot\n\n const changeMap = new Map<string, { baseValue: unknown; incomingValue: unknown }>()\n\n const incomingChanges = readActionLogChangesJson(incomingLog)\n if (incomingChanges) {\n for (const [fieldPathRaw, rawChange] of Object.entries(incomingChanges)) {\n const fieldPath = fieldPathRaw.trim()\n if (shouldSkipConflictField(fieldPath)) continue\n\n const changeRecord = isRecordValue(rawChange) ? rawChange : null\n const fromValue = changeRecord && Object.prototype.hasOwnProperty.call(changeRecord, 'from')\n ? changeRecord.from\n : readPathValueLoose(fallbackBaseSnapshot, fieldPath)\n const toValue = changeRecord && Object.prototype.hasOwnProperty.call(changeRecord, 'to')\n ? changeRecord.to\n : readPathValueLoose(incomingAfterSnapshot, fieldPath)\n\n changeMap.set(fieldPath, {\n baseValue: fromValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(fromValue),\n incomingValue: toValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(toValue),\n })\n }\n }\n\n if (!changeMap.size && fallbackBaseSnapshot && incomingAfterSnapshot) {\n const diffPaths = new Set<string>()\n this.collectSnapshotDiffPaths(\n fallbackBaseSnapshot,\n incomingAfterSnapshot,\n null,\n diffPaths,\n new Set<unknown>(),\n )\n\n for (const fieldPath of diffPaths) {\n if (shouldSkipConflictField(fieldPath)) continue\n const fromValue = readPathValueLoose(fallbackBaseSnapshot, fieldPath)\n const toValue = readPathValueLoose(incomingAfterSnapshot, fieldPath)\n changeMap.set(fieldPath, {\n baseValue: fromValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(fromValue),\n incomingValue: toValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(toValue),\n })\n }\n }\n\n if (!changeMap.size && mutationPayload && incomingAfterSnapshot) {\n for (const fieldPath of Object.keys(mutationPayload)) {\n if (shouldSkipConflictField(fieldPath)) continue\n const mineValue = readPathValueLoose(mutationPayload, fieldPath)\n const incomingValue = readPathValueLoose(incomingAfterSnapshot, fieldPath)\n if (mineValue === MISSING_CONFLICT_VALUE || incomingValue === MISSING_CONFLICT_VALUE) continue\n if (valuesEqual(mineValue, incomingValue)) continue\n const baseValue = readPathValueLoose(fallbackBaseSnapshot, fieldPath)\n changeMap.set(fieldPath, {\n baseValue: baseValue === MISSING_CONFLICT_VALUE ? null : normalizeConflictValue(baseValue),\n incomingValue: normalizeConflictValue(incomingValue),\n })\n }\n }\n\n if (!changeMap.size) return []\n\n const allFields = Array.from(changeMap.keys())\n const preferredFields = mutationPayload\n ? allFields.filter((fieldPath) => {\n const mineValue = readPathValueLoose(mutationPayload, fieldPath)\n if (mineValue === MISSING_CONFLICT_VALUE) return false\n const incomingValue = changeMap.get(fieldPath)?.incomingValue\n return !valuesEqual(mineValue, incomingValue)\n })\n : []\n const selectedFields = (preferredFields.length ? preferredFields : allFields)\n .filter((fieldPath) => !shouldSkipConflictField(fieldPath))\n .sort((left, right) => left.localeCompare(right))\n .slice(0, 25)\n\n return selectedFields.map((fieldPath) => {\n const entry = changeMap.get(fieldPath) ?? { baseValue: null, incomingValue: null }\n const mineValueRaw = mutationPayload ? readPathValueLoose(mutationPayload, fieldPath) : MISSING_CONFLICT_VALUE\n const mineValue = mineValueRaw === MISSING_CONFLICT_VALUE\n ? entry.baseValue\n : normalizeConflictValue(mineValueRaw)\n\n return {\n field: fieldPath,\n displayValue: normalizeConflictValue(entry.baseValue),\n baseValue: normalizeConflictValue(entry.baseValue),\n incomingValue: normalizeConflictValue(entry.incomingValue),\n mineValue: normalizeConflictValue(mineValue),\n }\n })\n }\n\n private async toConflictPayload(\n conflict: RecordLockConflict,\n mutationPayload: Record<string, unknown> | null,\n allowIncomingOverride: boolean,\n canOverrideIncoming: boolean,\n ): Promise<RecordLockConflictPayload> {\n const changes = await this.buildConflictChanges(conflict, mutationPayload)\n return {\n id: conflict.id,\n resourceKind: conflict.resourceKind,\n resourceId: conflict.resourceId,\n baseActionLogId: conflict.baseActionLogId,\n incomingActionLogId: conflict.incomingActionLogId,\n allowIncomingOverride,\n canOverrideIncoming,\n resolutionOptions: canOverrideIncoming ? ['accept_mine'] : [],\n changes,\n }\n }\n}\n\nexport function createRecordLockService(deps: RecordLockServiceDeps): RecordLockService {\n return new RecordLockService(deps)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["lock"]
7
7
  }
@@ -1,6 +1,8 @@
1
1
  import { buildNotificationFromType } from "@open-mercato/core/modules/notifications/lib/notificationBuilder";
2
2
  import { resolveNotificationService } from "@open-mercato/core/modules/notifications/lib/notificationService";
3
3
  import { ActionLog } from "@open-mercato/core/modules/audit_logs/data/entities";
4
+ import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
5
+ import { parseDecryptedFieldValue } from "@open-mercato/shared/lib/encryption/tenantDataEncryptionService";
4
6
  import {
5
7
  isConflictNotificationEnabled,
6
8
  resolveRecordLockNotificationType,
@@ -19,6 +21,15 @@ function formatChangedFieldLabel(rawField) {
19
21
  if (!words.length) return trimmedField;
20
22
  return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
21
23
  }
24
+ function readActionLogChanges(log) {
25
+ const rawChanges = log?.changesJson;
26
+ if (rawChanges && typeof rawChanges === "object" && !Array.isArray(rawChanges)) {
27
+ return rawChanges;
28
+ }
29
+ if (typeof rawChanges !== "string") return null;
30
+ const parsed = parseDecryptedFieldValue(rawChanges);
31
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
32
+ }
22
33
  async function handle(payload, ctx) {
23
34
  if (!payload.conflictActorUserId) return;
24
35
  const notificationsEnabled = await isConflictNotificationEnabled(ctx, {
@@ -28,8 +39,15 @@ async function handle(payload, ctx) {
28
39
  if (!notificationsEnabled) return;
29
40
  try {
30
41
  const em = ctx.resolve("em").fork();
31
- const incomingLog = payload.incomingActionLogId ? await em.findOne(ActionLog, { id: payload.incomingActionLogId, deletedAt: null }) : null;
32
- const changedFields = incomingLog?.changesJson && typeof incomingLog.changesJson === "object" ? Object.keys(incomingLog.changesJson).slice(0, 12).map(formatChangedFieldLabel).join(", ") : "";
42
+ const incomingLog = payload.incomingActionLogId ? await findOneWithDecryption(
43
+ em,
44
+ ActionLog,
45
+ { id: payload.incomingActionLogId, deletedAt: null },
46
+ void 0,
47
+ { tenantId: payload.tenantId, organizationId: payload.organizationId ?? null }
48
+ ) : null;
49
+ const changesJson = readActionLogChanges(incomingLog);
50
+ const changedFields = changesJson ? Object.keys(changesJson).slice(0, 12).map(formatChangedFieldLabel).join(", ") : "";
33
51
  const notificationService = resolveNotificationService(ctx);
34
52
  const typeDef = resolveRecordLockNotificationType("record_locks.conflict.detected");
35
53
  if (!typeDef) return;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/record_locks/subscribers/conflict-detected-notification.ts"],
4
- "sourcesContent": ["import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'\nimport { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport {\n isConflictNotificationEnabled,\n resolveRecordLockNotificationType,\n resolveRecordResourceLink,\n} from '../lib/notificationHelpers'\n\nexport const metadata = {\n event: 'record_locks.conflict.detected',\n persistent: true,\n id: 'record_locks:conflict-detected-notification',\n}\n\ntype Payload = {\n conflictId: string\n resourceKind: string\n resourceId: string\n tenantId: string\n organizationId?: string | null\n conflictActorUserId: string\n incomingActorUserId?: string | null\n baseActionLogId?: string | null\n incomingActionLogId?: string | null\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nfunction formatChangedFieldLabel(rawField: string): string {\n const trimmedField = rawField.trim()\n const withoutNamespace = trimmedField.includes('::') ? (trimmedField.split('::').pop() ?? trimmedField) : trimmedField\n const withoutPrefix = withoutNamespace.includes('.') ? (withoutNamespace.split('.').pop() ?? withoutNamespace) : withoutNamespace\n const words = withoutPrefix\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[._-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .split(' ')\n .filter(Boolean)\n\n if (!words.length) return trimmedField\n return words\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\nexport default async function handle(payload: Payload, ctx: ResolverContext) {\n if (!payload.conflictActorUserId) return\n\n const notificationsEnabled = await isConflictNotificationEnabled(ctx, {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n })\n if (!notificationsEnabled) return\n\n try {\n const em = (ctx.resolve('em') as EntityManager).fork()\n const incomingLog = payload.incomingActionLogId\n ? await em.findOne(ActionLog, { id: payload.incomingActionLogId, deletedAt: null })\n : null\n const changedFields = incomingLog?.changesJson && typeof incomingLog.changesJson === 'object'\n ? Object.keys(incomingLog.changesJson).slice(0, 12).map(formatChangedFieldLabel).join(', ')\n : ''\n\n const notificationService = resolveNotificationService(ctx)\n const typeDef = resolveRecordLockNotificationType('record_locks.conflict.detected')\n if (!typeDef) return\n\n const notificationInput = buildNotificationFromType(typeDef, {\n recipientUserId: payload.conflictActorUserId,\n bodyVariables: {\n resourceKind: payload.resourceKind,\n changedFields: changedFields || '-',\n },\n sourceEntityType: 'record_locks:conflict',\n sourceEntityId: payload.conflictId,\n linkHref: resolveRecordResourceLink(payload.resourceKind, payload.resourceId),\n groupKey: `record_locks.conflict.detected:${payload.conflictId}`,\n })\n\n await notificationService.create(notificationInput, {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n })\n } catch (error) {\n console.error('[record_locks:conflict-detected-notification] Failed to create notification:', error)\n }\n}\n"],
5
- "mappings": "AAAA,SAAS,iCAAiC;AAC1C,SAAS,kCAAkC;AAE3C,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAkBA,SAAS,wBAAwB,UAA0B;AACzD,QAAM,eAAe,SAAS,KAAK;AACnC,QAAM,mBAAmB,aAAa,SAAS,IAAI,IAAK,aAAa,MAAM,IAAI,EAAE,IAAI,KAAK,eAAgB;AAC1G,QAAM,gBAAgB,iBAAiB,SAAS,GAAG,IAAK,iBAAiB,MAAM,GAAG,EAAE,IAAI,KAAK,mBAAoB;AACjH,QAAM,QAAQ,cACX,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,WAAW,GAAG,EACtB,QAAQ,QAAQ,GAAG,EACnB,KAAK,EACL,MAAM,GAAG,EACT,OAAO,OAAO;AAEjB,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,SAAO,MACJ,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,GAAG;AACb;AAEA,eAAO,OAA8B,SAAkB,KAAsB;AAC3E,MAAI,CAAC,QAAQ,oBAAqB;AAElC,QAAM,uBAAuB,MAAM,8BAA8B,KAAK;AAAA,IACpE,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ,kBAAkB;AAAA,EAC5C,CAAC;AACD,MAAI,CAAC,qBAAsB;AAE3B,MAAI;AACF,UAAM,KAAM,IAAI,QAAQ,IAAI,EAAoB,KAAK;AACrD,UAAM,cAAc,QAAQ,sBACxB,MAAM,GAAG,QAAQ,WAAW,EAAE,IAAI,QAAQ,qBAAqB,WAAW,KAAK,CAAC,IAChF;AACJ,UAAM,gBAAgB,aAAa,eAAe,OAAO,YAAY,gBAAgB,WACjF,OAAO,KAAK,YAAY,WAAW,EAAE,MAAM,GAAG,EAAE,EAAE,IAAI,uBAAuB,EAAE,KAAK,IAAI,IACxF;AAEJ,UAAM,sBAAsB,2BAA2B,GAAG;AAC1D,UAAM,UAAU,kCAAkC,gCAAgC;AAClF,QAAI,CAAC,QAAS;AAEd,UAAM,oBAAoB,0BAA0B,SAAS;AAAA,MAC3D,iBAAiB,QAAQ;AAAA,MACzB,eAAe;AAAA,QACb,cAAc,QAAQ;AAAA,QACtB,eAAe,iBAAiB;AAAA,MAClC;AAAA,MACA,kBAAkB;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,UAAU,0BAA0B,QAAQ,cAAc,QAAQ,UAAU;AAAA,MAC5E,UAAU,kCAAkC,QAAQ,UAAU;AAAA,IAChE,CAAC;AAED,UAAM,oBAAoB,OAAO,mBAAmB;AAAA,MAClD,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ,kBAAkB;AAAA,IAC5C,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,gFAAgF,KAAK;AAAA,EACrG;AACF;",
4
+ "sourcesContent": ["import { buildNotificationFromType } from '@open-mercato/core/modules/notifications/lib/notificationBuilder'\nimport { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport {\n isConflictNotificationEnabled,\n resolveRecordLockNotificationType,\n resolveRecordResourceLink,\n} from '../lib/notificationHelpers'\n\nexport const metadata = {\n event: 'record_locks.conflict.detected',\n persistent: true,\n id: 'record_locks:conflict-detected-notification',\n}\n\ntype Payload = {\n conflictId: string\n resourceKind: string\n resourceId: string\n tenantId: string\n organizationId?: string | null\n conflictActorUserId: string\n incomingActorUserId?: string | null\n baseActionLogId?: string | null\n incomingActionLogId?: string | null\n}\n\ntype ResolverContext = {\n resolve: <T = unknown>(name: string) => T\n}\n\nfunction formatChangedFieldLabel(rawField: string): string {\n const trimmedField = rawField.trim()\n const withoutNamespace = trimmedField.includes('::') ? (trimmedField.split('::').pop() ?? trimmedField) : trimmedField\n const withoutPrefix = withoutNamespace.includes('.') ? (withoutNamespace.split('.').pop() ?? withoutNamespace) : withoutNamespace\n const words = withoutPrefix\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .replace(/[._-]+/g, ' ')\n .replace(/\\s+/g, ' ')\n .trim()\n .split(' ')\n .filter(Boolean)\n\n if (!words.length) return trimmedField\n return words\n .map((word) => word.charAt(0).toUpperCase() + word.slice(1))\n .join(' ')\n}\n\nfunction readActionLogChanges(log: ActionLog | null): Record<string, unknown> | null {\n const rawChanges = log?.changesJson as unknown\n if (rawChanges && typeof rawChanges === 'object' && !Array.isArray(rawChanges)) {\n return rawChanges as Record<string, unknown>\n }\n if (typeof rawChanges !== 'string') return null\n\n const parsed = parseDecryptedFieldValue(rawChanges)\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? parsed as Record<string, unknown>\n : null\n}\n\nexport default async function handle(payload: Payload, ctx: ResolverContext) {\n if (!payload.conflictActorUserId) return\n\n const notificationsEnabled = await isConflictNotificationEnabled(ctx, {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n })\n if (!notificationsEnabled) return\n\n try {\n const em = (ctx.resolve('em') as EntityManager).fork()\n const incomingLog = payload.incomingActionLogId\n ? await findOneWithDecryption(\n em,\n ActionLog,\n { id: payload.incomingActionLogId, deletedAt: null },\n undefined,\n { tenantId: payload.tenantId, organizationId: payload.organizationId ?? null },\n )\n : null\n const changesJson = readActionLogChanges(incomingLog)\n const changedFields = changesJson\n ? Object.keys(changesJson).slice(0, 12).map(formatChangedFieldLabel).join(', ')\n : ''\n\n const notificationService = resolveNotificationService(ctx)\n const typeDef = resolveRecordLockNotificationType('record_locks.conflict.detected')\n if (!typeDef) return\n\n const notificationInput = buildNotificationFromType(typeDef, {\n recipientUserId: payload.conflictActorUserId,\n bodyVariables: {\n resourceKind: payload.resourceKind,\n changedFields: changedFields || '-',\n },\n sourceEntityType: 'record_locks:conflict',\n sourceEntityId: payload.conflictId,\n linkHref: resolveRecordResourceLink(payload.resourceKind, payload.resourceId),\n groupKey: `record_locks.conflict.detected:${payload.conflictId}`,\n })\n\n await notificationService.create(notificationInput, {\n tenantId: payload.tenantId,\n organizationId: payload.organizationId ?? null,\n })\n } catch (error) {\n console.error('[record_locks:conflict-detected-notification] Failed to create notification:', error)\n }\n}\n"],
5
+ "mappings": "AAAA,SAAS,iCAAiC;AAC1C,SAAS,kCAAkC;AAE3C,SAAS,iBAAiB;AAC1B,SAAS,6BAA6B;AACtC,SAAS,gCAAgC;AACzC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEA,MAAM,WAAW;AAAA,EACtB,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,IAAI;AACN;AAkBA,SAAS,wBAAwB,UAA0B;AACzD,QAAM,eAAe,SAAS,KAAK;AACnC,QAAM,mBAAmB,aAAa,SAAS,IAAI,IAAK,aAAa,MAAM,IAAI,EAAE,IAAI,KAAK,eAAgB;AAC1G,QAAM,gBAAgB,iBAAiB,SAAS,GAAG,IAAK,iBAAiB,MAAM,GAAG,EAAE,IAAI,KAAK,mBAAoB;AACjH,QAAM,QAAQ,cACX,QAAQ,sBAAsB,OAAO,EACrC,QAAQ,WAAW,GAAG,EACtB,QAAQ,QAAQ,GAAG,EACnB,KAAK,EACL,MAAM,GAAG,EACT,OAAO,OAAO;AAEjB,MAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,SAAO,MACJ,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,GAAG;AACb;AAEA,SAAS,qBAAqB,KAAuD;AACnF,QAAM,aAAa,KAAK;AACxB,MAAI,cAAc,OAAO,eAAe,YAAY,CAAC,MAAM,QAAQ,UAAU,GAAG;AAC9E,WAAO;AAAA,EACT;AACA,MAAI,OAAO,eAAe,SAAU,QAAO;AAE3C,QAAM,SAAS,yBAAyB,UAAU;AAClD,SAAO,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,IAChE,SACA;AACN;AAEA,eAAO,OAA8B,SAAkB,KAAsB;AAC3E,MAAI,CAAC,QAAQ,oBAAqB;AAElC,QAAM,uBAAuB,MAAM,8BAA8B,KAAK;AAAA,IACpE,UAAU,QAAQ;AAAA,IAClB,gBAAgB,QAAQ,kBAAkB;AAAA,EAC5C,CAAC;AACD,MAAI,CAAC,qBAAsB;AAE3B,MAAI;AACF,UAAM,KAAM,IAAI,QAAQ,IAAI,EAAoB,KAAK;AACrD,UAAM,cAAc,QAAQ,sBACxB,MAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA,EAAE,IAAI,QAAQ,qBAAqB,WAAW,KAAK;AAAA,MACnD;AAAA,MACA,EAAE,UAAU,QAAQ,UAAU,gBAAgB,QAAQ,kBAAkB,KAAK;AAAA,IAC/E,IACA;AACJ,UAAM,cAAc,qBAAqB,WAAW;AACpD,UAAM,gBAAgB,cAClB,OAAO,KAAK,WAAW,EAAE,MAAM,GAAG,EAAE,EAAE,IAAI,uBAAuB,EAAE,KAAK,IAAI,IAC5E;AAEJ,UAAM,sBAAsB,2BAA2B,GAAG;AAC1D,UAAM,UAAU,kCAAkC,gCAAgC;AAClF,QAAI,CAAC,QAAS;AAEd,UAAM,oBAAoB,0BAA0B,SAAS;AAAA,MAC3D,iBAAiB,QAAQ;AAAA,MACzB,eAAe;AAAA,QACb,cAAc,QAAQ;AAAA,QACtB,eAAe,iBAAiB;AAAA,MAClC;AAAA,MACA,kBAAkB;AAAA,MAClB,gBAAgB,QAAQ;AAAA,MACxB,UAAU,0BAA0B,QAAQ,cAAc,QAAQ,UAAU;AAAA,MAC5E,UAAU,kCAAkC,QAAQ,UAAU;AAAA,IAChE,CAAC;AAED,UAAM,oBAAoB,OAAO,mBAAmB;AAAA,MAClD,UAAU,QAAQ;AAAA,MAClB,gBAAgB,QAAQ,kBAAkB;AAAA,IAC5C,CAAC;AAAA,EACH,SAAS,OAAO;AACd,YAAQ,MAAM,gFAAgF,KAAK;AAAA,EACrG;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/enterprise",
3
- "version": "0.6.1-develop.3081.21270ec58a",
3
+ "version": "0.6.1-develop.3090.06ab462170",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -64,8 +64,8 @@
64
64
  }
65
65
  },
66
66
  "dependencies": {
67
- "@open-mercato/core": "0.6.1-develop.3081.21270ec58a",
68
- "@open-mercato/ui": "0.6.1-develop.3081.21270ec58a",
67
+ "@open-mercato/core": "0.6.1-develop.3090.06ab462170",
68
+ "@open-mercato/ui": "0.6.1-develop.3090.06ab462170",
69
69
  "@simplewebauthn/browser": "^13.3.0",
70
70
  "@simplewebauthn/server": "^13.3.0",
71
71
  "@simplewebauthn/types": "^12.0.0",
@@ -75,10 +75,10 @@
75
75
  "qrcode": "^1.5.4"
76
76
  },
77
77
  "peerDependencies": {
78
- "@open-mercato/shared": "0.6.1-develop.3081.21270ec58a"
78
+ "@open-mercato/shared": "0.6.1-develop.3090.06ab462170"
79
79
  },
80
80
  "devDependencies": {
81
- "@open-mercato/shared": "0.6.1-develop.3081.21270ec58a",
81
+ "@open-mercato/shared": "0.6.1-develop.3090.06ab462170",
82
82
  "@types/jest": "^30.0.0",
83
83
  "jest": "^30.3.0",
84
84
  "ts-jest": "^29.4.9"
@@ -6,6 +6,8 @@ import type { ModuleConfigService } from '@open-mercato/core/modules/configs/lib
6
6
  import { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'
7
7
  import type { ActionLogService } from '@open-mercato/core/modules/audit_logs/services/actionLogService'
8
8
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
9
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
10
+ import { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'
9
11
  import { emitRecordLocksEvent } from '../events'
10
12
  import {
11
13
  RecordLock,
@@ -295,6 +297,29 @@ function isRecordValue(value: unknown): value is Record<string, unknown> {
295
297
  return Boolean(value && typeof value === 'object' && !Array.isArray(value))
296
298
  }
297
299
 
300
+ function parseDecryptedJsonLike(value: unknown): unknown {
301
+ return typeof value === 'string' ? parseDecryptedFieldValue(value) : value
302
+ }
303
+
304
+ function readJsonRecordValue(value: unknown): Record<string, unknown> | null {
305
+ const parsed = parseDecryptedJsonLike(value)
306
+ return isRecordValue(parsed) ? parsed : null
307
+ }
308
+
309
+ function readActionLogChangesJson(log: ActionLog | null): Record<string, unknown> | null {
310
+ return readJsonRecordValue(log?.changesJson ?? null)
311
+ }
312
+
313
+ function normalizeActionLogPayload(log: ActionLog | null): ActionLog | null {
314
+ if (!log) return null
315
+ log.changesJson = readJsonRecordValue(log.changesJson)
316
+ log.snapshotBefore = parseDecryptedJsonLike(log.snapshotBefore)
317
+ log.snapshotAfter = parseDecryptedJsonLike(log.snapshotAfter)
318
+ log.contextJson = readJsonRecordValue(log.contextJson)
319
+ log.commandPayload = parseDecryptedJsonLike(log.commandPayload)
320
+ return log
321
+ }
322
+
298
323
  function toIsoDate(value: unknown): string | null {
299
324
  if (value instanceof Date) {
300
325
  if (Number.isNaN(value.getTime())) return null
@@ -1491,7 +1516,13 @@ export class RecordLockService {
1491
1516
  where.organizationId = normalizeScopeOrganization(input.organizationId)
1492
1517
  }
1493
1518
 
1494
- return this.em.findOne(ActionLog, where, { orderBy: { createdAt: 'desc' } })
1519
+ return normalizeActionLogPayload(await findOneWithDecryption(
1520
+ this.em,
1521
+ ActionLog,
1522
+ where,
1523
+ { orderBy: { createdAt: 'desc' } },
1524
+ { tenantId: input.tenantId, organizationId: normalizeScopeOrganization(input.organizationId) },
1525
+ ))
1495
1526
  }
1496
1527
 
1497
1528
  private async findLatestActionLogWithScopeFallback(
@@ -1524,14 +1555,21 @@ export class RecordLockService {
1524
1555
  where.organizationId = normalizeScopeOrganization(input.organizationId)
1525
1556
  }
1526
1557
 
1527
- return this.em.findOne(ActionLog, where, { orderBy: { createdAt: 'desc' } })
1558
+ return normalizeActionLogPayload(await findOneWithDecryption(
1559
+ this.em,
1560
+ ActionLog,
1561
+ where,
1562
+ { orderBy: { createdAt: 'desc' } },
1563
+ { tenantId: input.tenantId, organizationId: normalizeScopeOrganization(input.organizationId) },
1564
+ ))
1528
1565
  }
1529
1566
 
1530
1567
  private summarizeChangedFieldsFromActionLog(log: ActionLog | null): string {
1531
1568
  if (!log) return ''
1532
1569
 
1533
- if (isRecordValue(log.changesJson)) {
1534
- const fromChanges = Object.keys(log.changesJson)
1570
+ const changesJson = readActionLogChangesJson(log)
1571
+ if (changesJson) {
1572
+ const fromChanges = Object.keys(changesJson)
1535
1573
  .filter((field) => !shouldSkipConflictField(field))
1536
1574
  .slice(0, 12)
1537
1575
  .map(formatChangedFieldLabel)
@@ -1539,8 +1577,8 @@ export class RecordLockService {
1539
1577
  if (fromChanges) return fromChanges
1540
1578
  }
1541
1579
 
1542
- const before = isRecordValue(log.snapshotBefore) ? log.snapshotBefore : null
1543
- const after = isRecordValue(log.snapshotAfter) ? log.snapshotAfter : null
1580
+ const before = readJsonRecordValue(log.snapshotBefore)
1581
+ const after = readJsonRecordValue(log.snapshotAfter)
1544
1582
  if (!before || !after) return ''
1545
1583
 
1546
1584
  const diffPaths = new Set<string>()
@@ -1559,10 +1597,11 @@ export class RecordLockService {
1559
1597
  incoming: string
1560
1598
  current: string
1561
1599
  }> {
1562
- if (!log || !isRecordValue(log.changesJson)) return []
1600
+ const changesJson = readActionLogChangesJson(log)
1601
+ if (!changesJson) return []
1563
1602
 
1564
1603
  const rows: Array<{ field: string; incoming: string; current: string }> = []
1565
- for (const [rawField, rawChange] of Object.entries(log.changesJson)) {
1604
+ for (const [rawField, rawChange] of Object.entries(changesJson)) {
1566
1605
  if (rows.length >= 12) break
1567
1606
  if (shouldSkipConflictField(rawField)) continue
1568
1607
 
@@ -1792,8 +1831,15 @@ export class RecordLockService {
1792
1831
  ? await this.actionLogService.findById(logId)
1793
1832
  : null
1794
1833
  if (!resolved) {
1795
- resolved = await this.em.findOne(ActionLog, { id: logId, deletedAt: null })
1834
+ resolved = await findOneWithDecryption(
1835
+ this.em,
1836
+ ActionLog,
1837
+ { id: logId, deletedAt: null },
1838
+ undefined,
1839
+ { tenantId: scope.tenantId, organizationId: normalizeScopeOrganization(scope.organizationId) },
1840
+ )
1796
1841
  }
1842
+ resolved = normalizeActionLogPayload(resolved)
1797
1843
  if (!resolved || resolved.deletedAt) return null
1798
1844
 
1799
1845
  if (resolved.tenantId !== scope.tenantId) return null
@@ -1857,14 +1903,14 @@ export class RecordLockService {
1857
1903
  const baseLog = await this.findActionLogById(conflict.baseActionLogId, scope)
1858
1904
  const incomingLog = await this.findActionLogById(conflict.incomingActionLogId, scope)
1859
1905
 
1860
- const baseSnapshot = isRecordValue(baseLog?.snapshotAfter) ? baseLog.snapshotAfter : null
1861
- const incomingBeforeSnapshot = isRecordValue(incomingLog?.snapshotBefore) ? incomingLog.snapshotBefore : null
1862
- const incomingAfterSnapshot = isRecordValue(incomingLog?.snapshotAfter) ? incomingLog.snapshotAfter : null
1906
+ const baseSnapshot = readJsonRecordValue(baseLog?.snapshotAfter ?? null)
1907
+ const incomingBeforeSnapshot = readJsonRecordValue(incomingLog?.snapshotBefore ?? null)
1908
+ const incomingAfterSnapshot = readJsonRecordValue(incomingLog?.snapshotAfter ?? null)
1863
1909
  const fallbackBaseSnapshot = baseSnapshot ?? incomingBeforeSnapshot
1864
1910
 
1865
1911
  const changeMap = new Map<string, { baseValue: unknown; incomingValue: unknown }>()
1866
1912
 
1867
- const incomingChanges = isRecordValue(incomingLog?.changesJson) ? incomingLog.changesJson : null
1913
+ const incomingChanges = readActionLogChangesJson(incomingLog)
1868
1914
  if (incomingChanges) {
1869
1915
  for (const [fieldPathRaw, rawChange] of Object.entries(incomingChanges)) {
1870
1916
  const fieldPath = fieldPathRaw.trim()
@@ -2,6 +2,8 @@ import { buildNotificationFromType } from '@open-mercato/core/modules/notificati
2
2
  import { resolveNotificationService } from '@open-mercato/core/modules/notifications/lib/notificationService'
3
3
  import type { EntityManager } from '@mikro-orm/postgresql'
4
4
  import { ActionLog } from '@open-mercato/core/modules/audit_logs/data/entities'
5
+ import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
6
+ import { parseDecryptedFieldValue } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'
5
7
  import {
6
8
  isConflictNotificationEnabled,
7
9
  resolveRecordLockNotificationType,
@@ -48,6 +50,19 @@ function formatChangedFieldLabel(rawField: string): string {
48
50
  .join(' ')
49
51
  }
50
52
 
53
+ function readActionLogChanges(log: ActionLog | null): Record<string, unknown> | null {
54
+ const rawChanges = log?.changesJson as unknown
55
+ if (rawChanges && typeof rawChanges === 'object' && !Array.isArray(rawChanges)) {
56
+ return rawChanges as Record<string, unknown>
57
+ }
58
+ if (typeof rawChanges !== 'string') return null
59
+
60
+ const parsed = parseDecryptedFieldValue(rawChanges)
61
+ return parsed && typeof parsed === 'object' && !Array.isArray(parsed)
62
+ ? parsed as Record<string, unknown>
63
+ : null
64
+ }
65
+
51
66
  export default async function handle(payload: Payload, ctx: ResolverContext) {
52
67
  if (!payload.conflictActorUserId) return
53
68
 
@@ -60,10 +75,17 @@ export default async function handle(payload: Payload, ctx: ResolverContext) {
60
75
  try {
61
76
  const em = (ctx.resolve('em') as EntityManager).fork()
62
77
  const incomingLog = payload.incomingActionLogId
63
- ? await em.findOne(ActionLog, { id: payload.incomingActionLogId, deletedAt: null })
78
+ ? await findOneWithDecryption(
79
+ em,
80
+ ActionLog,
81
+ { id: payload.incomingActionLogId, deletedAt: null },
82
+ undefined,
83
+ { tenantId: payload.tenantId, organizationId: payload.organizationId ?? null },
84
+ )
64
85
  : null
65
- const changedFields = incomingLog?.changesJson && typeof incomingLog.changesJson === 'object'
66
- ? Object.keys(incomingLog.changesJson).slice(0, 12).map(formatChangedFieldLabel).join(', ')
86
+ const changesJson = readActionLogChanges(incomingLog)
87
+ const changedFields = changesJson
88
+ ? Object.keys(changesJson).slice(0, 12).map(formatChangedFieldLabel).join(', ')
67
89
  : ''
68
90
 
69
91
  const notificationService = resolveNotificationService(ctx)