@open-mercato/core 0.10.1-develop.7752.1.fd7c06569b → 0.10.1-develop.7756.1.8f5dec1b96
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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/helpers/integration/dbFixtures.js +1 -1
- package/dist/helpers/integration/dbFixtures.js.map +1 -1
- package/dist/modules/auth/api/roles/acl/route.js +2 -2
- package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
- package/dist/modules/auth/api/roles/route.js +1 -1
- package/dist/modules/auth/api/roles/route.js.map +2 -2
- package/dist/modules/auth/api/users/acl/route.js +10 -2
- package/dist/modules/auth/api/users/acl/route.js.map +2 -2
- package/dist/modules/auth/commands/acl.js +2 -2
- package/dist/modules/auth/commands/acl.js.map +2 -2
- package/dist/modules/auth/commands/roles.js +1 -1
- package/dist/modules/auth/commands/roles.js.map +2 -2
- package/dist/modules/auth/commands/users.js +7 -3
- package/dist/modules/auth/commands/users.js.map +2 -2
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/lib/executionPrincipal.js +2 -2
- package/dist/modules/auth/lib/executionPrincipal.js.map +2 -2
- package/dist/modules/auth/lib/grantChecks.js +38 -11
- package/dist/modules/auth/lib/grantChecks.js.map +2 -2
- package/dist/modules/auth/lib/setup-app.js +2 -2
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/auth/migrations/Migration20260828120000_auth.js +45 -0
- package/dist/modules/auth/migrations/Migration20260828120000_auth.js.map +7 -0
- package/dist/modules/auth/services/rbacService.js +138 -59
- package/dist/modules/auth/services/rbacService.js.map +3 -3
- package/dist/modules/entities/cli.js +12 -1
- package/dist/modules/entities/cli.js.map +2 -2
- package/dist/modules/entities/lib/helpers.js +13 -0
- package/dist/modules/entities/lib/helpers.js.map +2 -2
- package/dist/modules/entities/lib/rotate-custom-entity-documents.js +157 -0
- package/dist/modules/entities/lib/rotate-custom-entity-documents.js.map +7 -0
- package/dist/modules/notifications/lib/notificationRecipients.js +2 -2
- package/dist/modules/notifications/lib/notificationRecipients.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +179 -11
- package/dist/modules/query_index/lib/engine.js.map +3 -3
- package/package.json +7 -7
- package/src/helpers/integration/dbFixtures.ts +1 -1
- package/src/modules/auth/api/roles/acl/route.ts +2 -2
- package/src/modules/auth/api/roles/route.ts +1 -1
- package/src/modules/auth/api/users/acl/route.ts +10 -2
- package/src/modules/auth/commands/acl.ts +2 -2
- package/src/modules/auth/commands/roles.ts +1 -1
- package/src/modules/auth/commands/users.ts +7 -3
- package/src/modules/auth/data/entities.ts +12 -0
- package/src/modules/auth/lib/executionPrincipal.ts +2 -2
- package/src/modules/auth/lib/grantChecks.ts +115 -11
- package/src/modules/auth/lib/setup-app.ts +2 -2
- package/src/modules/auth/migrations/Migration20260828120000_auth.ts +70 -0
- package/src/modules/auth/services/rbacService.ts +161 -76
- package/src/modules/entities/cli.ts +12 -1
- package/src/modules/entities/lib/helpers.ts +17 -0
- package/src/modules/entities/lib/rotate-custom-entity-documents.ts +200 -0
- package/src/modules/notifications/lib/notificationRecipients.ts +6 -0
- package/src/modules/query_index/lib/engine.ts +187 -9
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/modules/entities/cli.ts"],
|
|
4
|
-
"sourcesContent": ["import { getCliModules, getDefaultEncryptionMaps, type Module, type ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CacheStrategy } from '@open-mercato/cache/types'\nimport { CustomEntity, CustomFieldDef, EncryptionMap } from './data/entities'\nimport {\n upsertCanonicalEncryptionMap,\n upsertEncryptionMapSpecs,\n upsertEncryptionMaps,\n invalidateEncryptionMapScopes,\n} from './lib/encryption-maps'\nimport {\n installCustomEntitiesFromModules,\n getAggregatedCustomEntityConfigs,\n} from './lib/install-from-ce'\nimport readline from 'node:readline/promises'\nimport { stdin as input, stdout as output } from 'node:process'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'\nimport { createKmsService, type KmsService, type TenantDek } from '@open-mercato/shared/lib/encryption/kms'\nimport {\n decryptWithAesGcm,\n decryptWithAesGcmStrict,\n isEncryptedPayloadShape,\n TenantDataEncryptionError,\n TenantDataEncryptionErrorCode,\n} from '@open-mercato/shared/lib/encryption/aes'\nimport {\n TenantDataEncryptionService,\n parseDecryptedFieldValue,\n resolveEncryptionKeyId,\n} from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { resolveEntityIdFromMetadata } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { listEntityMetadata } from '@open-mercato/shared/lib/db/entityMetadata'\nimport { Organization } from '../directory/data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport crypto from 'node:crypto'\n\nfunction parseArgs(rest: string[]) {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i]\n if (!a) continue\n if (a.startsWith('--')) {\n const [k, v] = a.replace(/^--/, '').split('=')\n if (v !== undefined) args[k] = v\n else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) { args[k] = rest[i + 1]!; i++ }\n else args[k] = true\n }\n }\n return args\n}\n\nconst seedDefs: ModuleCli = {\n command: 'install',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantIdArg = (args.tenant as string) || (args.tenantId as string)\n const globalOnly = Boolean(args.global)\n const dry = Boolean(args['dry-run'] || args.dry)\n const force = Boolean(args.force)\n const includeGlobal = args['no-global'] ? false : true\n\n if (globalOnly && includeGlobal === false) {\n console.error('Cannot combine --global with --no-global.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n let cache: CacheStrategy | null = null\n try { cache = resolve('cache') as CacheStrategy } catch {}\n\n const tenantIds = tenantIdArg\n ? [tenantIdArg]\n : (globalOnly ? [] : undefined)\n\n const logger = (message: string) => {\n const prefix = dry ? '[dry-run] ' : ''\n console.log(`${prefix}${message}`)\n }\n\n const result = await installCustomEntitiesFromModules(em, cache, {\n tenantIds,\n includeGlobal,\n dryRun: dry,\n force,\n logger,\n })\n const label = dry ? 'Dry-run' : 'Sync'\n console.log(`\u2705 ${label} complete: processed=${result.processed}, updated=${result.synchronized}, fieldsChanged=${result.fieldChanges}, skipped=${result.skipped}`)\n },\n}\n\n// Reinstall: remove existing definitions for target scope and re-seed from modules\nconst reinstallDefs: ModuleCli = {\n command: 'reinstall',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantIdArg = (args.tenant as string) || (args.tenantId as string)\n const globalOnly = Boolean(args.global)\n const dry = Boolean(args['dry-run'] || args.dry)\n const includeGlobal = globalOnly ? true : (args['no-global'] ? false : true)\n\n if (globalOnly && includeGlobal === false) {\n console.error('Cannot combine --global with --no-global.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n let cache: CacheStrategy | null = null\n try { cache = resolve('cache') as CacheStrategy } catch {}\n\n const tenantIds = tenantIdArg\n ? [tenantIdArg]\n : (globalOnly ? [] : undefined)\n\n const aggregates = getAggregatedCustomEntityConfigs()\n const relevant = aggregates.filter((entry) => (globalOnly ? entry.spec?.global === true : true))\n if (!relevant.length) {\n console.log('No custom entities or fields discovered. Nothing to reinstall.')\n return\n }\n const entityIds = Array.from(new Set(relevant.map((entry) => entry.entityId)))\n if (!entityIds.length) {\n console.log('No entity ids discovered. Nothing to reinstall.')\n return\n }\n\n const logger = (message: string) => {\n const prefix = dry ? '[dry-run] ' : ''\n console.log(`${prefix}${message}`)\n }\n\n if (dry) {\n console.log('Dry-run: would remove existing custom entity definitions before reinstall.')\n } else {\n const fieldWhere: any = { entityId: { $in: entityIds } }\n if (tenantIds !== undefined) {\n if (tenantIds.length === 0) fieldWhere.tenantId = null\n else fieldWhere.tenantId = { $in: tenantIds }\n }\n const removedFields = await em.nativeDelete(CustomFieldDef, fieldWhere)\n\n const entityWhere: any = { entityId: { $in: entityIds } }\n if (tenantIds !== undefined) {\n if (tenantIds.length === 0) entityWhere.tenantId = null\n else entityWhere.tenantId = { $in: tenantIds }\n }\n const removedEntities = await em.nativeDelete(CustomEntity, entityWhere)\n\n if (cache && entityIds.length) {\n try {\n await cache.deleteByTags(entityIds.map((id) => `custom-entity:${id}`))\n } catch {}\n }\n console.log(`Cleared definitions: fields=${removedFields}, entities=${removedEntities}`)\n }\n\n const result = await installCustomEntitiesFromModules(em, cache, {\n tenantIds,\n includeGlobal,\n dryRun: dry,\n force: true,\n logger,\n })\n const label = dry ? 'Dry-run' : 'Reinstall'\n console.log(`\u2705 ${label} complete: processed=${result.processed}, updated=${result.synchronized}, fieldsChanged=${result.fieldChanges}, skipped=${result.skipped}`)\n },\n}\n\n// Interactive: add a single custom field definition\nconst addField: ModuleCli = {\n command: 'add-field',\n async run(rest) {\n const args = parseArgs(rest)\n const rl = readline.createInterface({ input, output })\n const ask = async (q: string, d?: string) => {\n const a = (await rl.question(d ? `${q} [${d}]: ` : `${q}: `)).trim()\n return a || (d ?? '')\n }\n const askBool = async (q: string, d = false) => {\n const a = (await ask(q, d ? 'y' : 'n')).toLowerCase()\n return parseBooleanToken(a) === true\n }\n\n try {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n const entityId = (args.entity as string) || (args.e as string) || await ask('Entity ID (e.g., example:todo)')\n const isGlobal = args.global ? true : await askBool('Global (no organization)?', false)\n const orgId = isGlobal ? null : ((args.org as string) || (args.organizationId as string) || await ask('Organization ID'))\n const tenantId = isGlobal ? null : ((args.tenant as string) || (args.tenantId as string) || await ask('Tenant ID'))\n const key = (args.key as string) || await ask('Field key (snake_case)')\n let kind = (args.kind as string) || await ask(\"Kind (text|multiline|integer|float|boolean|select|currency|relation|attachment)\", 'text')\n kind = kind.toLowerCase()\n if (!['text','multiline','integer','float','boolean','select','currency','relation','attachment'].includes(kind)) throw new Error('Invalid kind')\n const label = (args.label as string) || (await ask('Label', key))\n const description = (args.description as string) || ''\n const required = args.required !== undefined ? Boolean(args.required) : await askBool('Required?', false)\n const multi = args.multi !== undefined ? Boolean(args.multi) : await askBool('Allow multiple?', false)\n let options: string[] | undefined\n if (kind === 'select') {\n const raw = (args.options as string) || await ask('Options (comma-separated)', 'low,medium,high')\n options = parseCommaSeparatedList(raw)\n }\n let defaultValue: any = undefined\n const defRaw = (args.default as string) ?? (args.defaultValue as string)\n const needDefault = defRaw !== undefined ? defRaw : await ask('Default value (leave empty for none)', '')\n if (needDefault !== '') {\n switch (kind) {\n case 'integer': defaultValue = Number(needDefault); break\n case 'float': defaultValue = Number(needDefault); break\n case 'boolean': defaultValue = parseBooleanToken(String(needDefault)) === true; break\n default: defaultValue = String(needDefault)\n }\n }\n const filterable = args.filterable !== undefined ? Boolean(args.filterable) : await askBool('Filterable?', true)\n const listVisible = args.listVisible !== undefined ? Boolean(args.listVisible) : await askBool('Visible in list?', true)\n const formEditable = args.formEditable !== undefined ? Boolean(args.formEditable) : await askBool('Editable in forms?', true)\n const indexed = args.indexed !== undefined ? Boolean(args.indexed) : await askBool('Indexed?', false)\n\n const where = { entityId, organizationId: orgId, tenantId: tenantId, key }\n const existing = await em.findOne(CustomFieldDef, where)\n const configJson: any = {}\n if (options) configJson.options = options\n if (defaultValue !== undefined) configJson.defaultValue = defaultValue\n if (required !== undefined) configJson.required = required\n if (multi !== undefined) configJson.multi = multi\n if (filterable !== undefined) configJson.filterable = filterable\n if (indexed !== undefined) configJson.indexed = indexed\n if (listVisible !== undefined) configJson.listVisible = listVisible\n if (formEditable !== undefined) configJson.formEditable = formEditable\n if (label !== undefined) configJson.label = label\n if (description !== undefined) configJson.description = description\n\n if (!existing) {\n await em.persist(em.create(CustomFieldDef, {\n entityId,\n organizationId: orgId,\n tenantId: tenantId,\n key,\n kind,\n configJson,\n isActive: true,\n })).flush()\n console.log(`Created custom field: ${entityId}.${key} (${kind})${orgId == null ? ' [global]' : ` [org=${orgId}, tenant=${tenantId}]`}`)\n } else {\n existing.kind = kind as any\n existing.configJson = configJson\n existing.isActive = true\n await em.flush()\n console.log(`Updated custom field: ${entityId}.${key} (${kind})${orgId == null ? ' [global]' : ` [org=${orgId}, tenant=${tenantId}]`}`)\n }\n } catch (e: any) {\n console.error('Failed:', e?.message || e)\n } finally {\n await rl.close()\n }\n },\n}\n\n// Re-exported for backward compatibility: callers importing these from `./cli` keep working.\n// `upsertEncryptionMapSpecs`/`upsertEncryptionMaps`/`invalidateEncryptionMapScopes` live in\n// `./lib/encryption-maps` (kept lean so the subscriber that seeds a new organization's maps does\n// not pull in this whole CLI module's dependency graph). `resolveEncryptionMapModules` stays\n// defined here: it reads `getCliModules()`, and `cli-registry-boundary.test.ts` in\n// @open-mercato/shared only allows that from `packages/cli/**` or a file literally named\n// `cli.ts` \u2014 a lib file could not host it.\nexport { upsertEncryptionMapSpecs, upsertEncryptionMaps, invalidateEncryptionMapScopes }\n\nasync function resolveEncryptionMapModules(): Promise<Module[]> {\n const cliModules = getCliModules()\n if (cliModules.length > 0) return cliModules\n try {\n const { getModules } = await import('@open-mercato/shared/lib/modules/registry')\n return getModules()\n } catch {\n return []\n }\n}\n\n/**\n * Entity ids whose encryption map is declared in module code with `keyScope: 'system'`.\n *\n * Their ciphertext is sealed under a `system:<entityId>` DEK that has no tenant, so every\n * tenant-scoped command here MUST leave them alone: re-wrapping such a value under a tenant\n * DEK produces a payload runtime decryption can never read again. `backfill-system-encryption`\n * is the one command that handles them.\n */\nasync function getSystemScopedEntityIds(): Promise<Set<string>> {\n return new Set(\n getDefaultEncryptionMaps(await resolveEncryptionMapModules())\n .filter((map) => map.keyScope === 'system')\n .map((map) => map.entityId),\n )\n}\n\nconst seedEncryptionMaps: ModuleCli = {\n command: 'seed-encryption',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = (args.tenant as string) || (args.tenantId as string)\n const organizationId = (args.org as string) || (args.organization as string) || (args.organizationId as string) || null\n\n if (!tenantId) {\n console.error('tenant id is required (use --tenant <uuid>)')\n return\n }\n if (!isTenantDataEncryptionEnabled()) {\n console.warn('TENANT_DATA_ENCRYPTION is disabled; skipping encryption map seeding.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const logger = (msg: string) => console.log(msg)\n const encryptionService = resolve('tenantEncryptionService') as Pick<TenantDataEncryptionService, 'invalidateMap'>\n await upsertEncryptionMaps(em, tenantId, organizationId, await resolveEncryptionMapModules(), logger, async (scope) => {\n await invalidateEncryptionMapScopes(encryptionService, [scope])\n })\n console.log('\u2705 Encryption maps seeded')\n },\n}\n\nfunction normalizeKeyInput(value: string): string {\n return value.trim().replace(/(?:^['\"]|['\"]$)/g, '')\n}\n\nclass DerivedKeyKmsService implements KmsService {\n private root: Buffer\n constructor(secret: string) {\n this.root = crypto.createHash('sha256').update(normalizeKeyInput(secret)).digest()\n }\n\n isHealthy(): boolean {\n return true\n }\n\n private deriveKey(tenantId: string): string {\n const iterations = 310_000\n const keyLength = 32\n const derived = crypto.pbkdf2Sync(this.root, tenantId, iterations, keyLength, 'sha512')\n return derived.toString('base64')\n }\n\n async getTenantDek(tenantId: string): Promise<TenantDek | null> {\n if (!tenantId) return null\n return { tenantId, key: this.deriveKey(tenantId), fetchedAt: Date.now() }\n }\n\n async createTenantDek(tenantId: string): Promise<TenantDek | null> {\n return this.getTenantDek(tenantId)\n }\n}\n\nfunction fingerprintDek(dek: TenantDek | null): string | null {\n if (!dek?.key) return null\n return crypto.createHash('sha256').update(dek.key).digest('hex').slice(0, 12)\n}\n\nfunction decryptWithOldKey(\n payload: string,\n dek: TenantDek | null,\n): string | null {\n if (!dek?.key) return null\n return decryptWithAesGcm(payload, dek.key)\n}\n\nfunction resolveProperty(meta: any, field: string): { columnName: string | null; prop: any | null } {\n if (!meta?.properties) return { columnName: null, prop: null }\n const candidates = [\n field,\n field.replace(/_([a-z])/g, (_, c) => c.toUpperCase()),\n field.replace(/([A-Z])/g, '_$1').toLowerCase(),\n ]\n for (const candidate of candidates) {\n const prop = meta.properties[candidate]\n const fieldName =\n prop?.fieldName ??\n (Array.isArray(prop?.fieldNames) && prop.fieldNames.length ? prop.fieldNames[0] : undefined)\n if (typeof fieldName === 'string' && fieldName.length) return { columnName: fieldName, prop }\n if (prop?.name) return { columnName: prop.name, prop }\n }\n return { columnName: null, prop: null }\n}\n\nfunction buildEntityMetaRegistry(em: any): Map<string, any> {\n const allMeta = listEntityMetadata(em)\n const metaByEntityId = new Map<string, any>()\n for (const meta of allMeta) {\n const resolved = resolveEntityIdFromMetadata(meta)\n if (resolved) metaByEntityId.set(resolved, meta)\n }\n return metaByEntityId\n}\n\ninterface EncryptionMapMeta {\n entityId: string\n meta: any\n fields: Array<{ field: string; hashField?: string | null }>\n tenantId: string\n}\n\nfunction resolveMapMeta(\n map: EncryptionMap,\n metaByEntityId: Map<string, any>,\n warn: (msg: string) => void = () => {},\n): EncryptionMapMeta | null {\n const entityId = String(map.entityId)\n const meta = metaByEntityId.get(entityId)\n if (!meta) {\n warn(`Skipping ${entityId}: metadata not found.`)\n return null\n }\n const fields = Array.isArray(map.fieldsJson) ? map.fieldsJson : []\n if (!fields.length) return null\n const tenantId = map.tenantId ? String(map.tenantId) : null\n if (!tenantId) return null\n return { entityId, meta, fields, tenantId }\n}\n\nfunction formatValueForColumn(prop: any, value: unknown): unknown {\n if (value === null || value === undefined) return value\n const types = Array.isArray(prop?.columnTypes) ? prop.columnTypes : []\n const type = String(prop?.type ?? '').toLowerCase()\n const isJson = types.some((entry: string) => entry.toLowerCase().includes('json')) || type === 'json' || type === 'jsonb'\n if (!isJson) return value\n return JSON.stringify(value)\n}\n\nconst rotateEncryptionKey: ModuleCli = {\n command: 'rotate-encryption-key',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantIdArg = (args.tenant as string) || (args.tenantId as string) || null\n const organizationIdArg = (args.org as string) || (args.organization as string) || (args.organizationId as string) || null\n const oldKey = (args['old-key'] as string) || (args.oldKey as string) || null\n const dryRun = Boolean(args['dry-run'] || args.dry)\n const debug = Boolean(args.debug)\n const rotate = Boolean(oldKey)\n if (rotate && !tenantIdArg) {\n console.warn(\n '\u26A0\uFE0F Rotating with --old-key across all tenants. A single old key should normally target one tenant; consider --tenant.',\n )\n }\n if (!isTenantDataEncryptionEnabled()) {\n console.error('TENANT_DATA_ENCRYPTION is disabled; aborting.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const conn: any = em?.getConnection?.()\n if (!conn || typeof conn.execute !== 'function') {\n console.error('Unable to access raw connection; aborting.')\n return\n }\n\n const encryptionService = new TenantDataEncryptionService(em as any, { kms: createKmsService() })\n const oldKms = rotate && oldKey ? new DerivedKeyKmsService(oldKey) : null\n if (!encryptionService.isEnabled()) {\n console.error('Encryption service is not enabled (KMS unhealthy or no DEK). Aborting.')\n return\n }\n\n if (debug) {\n console.log('[rotate-encryption-key]', {\n hasOldKey: Boolean(oldKey),\n rotate,\n tenantId: tenantIdArg ?? null,\n organizationId: organizationIdArg ?? null,\n })\n if (tenantIdArg) {\n const [oldDek, newDek] = await Promise.all([\n oldKms?.getTenantDek(tenantIdArg) ?? Promise.resolve(null),\n encryptionService.getDek(tenantIdArg),\n ])\n console.log('[rotate-encryption-key] dek fingerprints', {\n oldKey: fingerprintDek(oldDek),\n currentKey: fingerprintDek(newDek),\n })\n } else {\n console.log('[rotate-encryption-key] dek fingerprints skipped (no tenantId)')\n }\n }\n\n const metaByEntityId = buildEntityMetaRegistry(em)\n\n const where: any = { deletedAt: null }\n if (tenantIdArg) where.tenantId = tenantIdArg\n if (organizationIdArg) where.organizationId = organizationIdArg\n const allMaps = await em.find(EncryptionMap, where)\n const systemScopedEntityIds = await getSystemScopedEntityIds()\n const maps = allMaps.filter((map: EncryptionMap) => {\n if (!systemScopedEntityIds.has(String(map.entityId))) return true\n console.warn(\n `Skipping ${map.entityId}: system-scoped entity. Its ciphertext is sealed under a system key and cannot be rotated with a tenant key \u2014 use \"mercato entities backfill-system-encryption\" instead.`,\n )\n return false\n })\n if (!maps.length) {\n console.log('No encryption maps found for the selected scope.')\n return\n }\n\n const resolveScopes = async (tenantId: string, organizationId: string | null) => {\n if (organizationId) return [{ tenantId, organizationId }]\n const orgs = await em.find(Organization, { tenant: tenantId })\n const scopes = orgs.map((org: Organization) => ({\n tenantId,\n organizationId: String(org.id),\n }))\n scopes.push({ tenantId, organizationId: null })\n return scopes\n }\n\n const oldDekCache = new Map<string, TenantDek | null>()\n // Rows this run could not rotate because their ciphertext opens under neither\n // --old-key nor the current tenant key \u2014 surfaced in the closing summary so an\n // operator does not have to grep console.warn output for a batch of thousands.\n const unrecoverableRows: Array<{ entityId: string; field: string; rowId: unknown }> = []\n // A dry run must not provision key material. `encryptEntityPayload` creates and\n // persists a tenant DEK in KMS/Vault the first time it runs for a tenant, so a\n // read-only preview would silently mutate KMS state (#5950). Probe read-only\n // once per tenant instead, and report what a real run would rewrite.\n //\n // The tenant id IS the key id here: this command skips every system-scoped\n // entity above, and its service is built without `defaultEncryptionMaps`, so\n // no map it sees can resolve to a `system:<entityId>` key.\n const dekAvailability = new Map<string, boolean>()\n const hasExistingDek = async (tenantId: string): Promise<boolean> => {\n const cached = dekAvailability.get(tenantId)\n if (cached !== undefined) return cached\n const available = Boolean(await encryptionService.getDek(tenantId))\n dekAvailability.set(tenantId, available)\n if (!available) {\n console.warn(\n `[dry-run] Tenant ${tenantId} has no data-encryption key yet. Reporting the rows a real run would encrypt; no key material was provisioned.`,\n )\n }\n return available\n }\n const processScope = async (\n entityId: string,\n meta: any,\n fields: Array<{ field: string; hashField?: string | null }>,\n scope: { tenantId: string; organizationId: string | null },\n ): Promise<number> => {\n const pk = Array.isArray(meta?.primaryKeys) && meta.primaryKeys.length ? meta.primaryKeys[0] : 'id'\n const columns = new Set<string>()\n columns.add(pk)\n for (const rule of fields) {\n const resolved = resolveProperty(meta, rule.field)\n if (resolved?.columnName) columns.add(resolved.columnName)\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n if (resolvedHash?.columnName) columns.add(resolvedHash.columnName)\n }\n }\n const columnList = Array.from(columns)\n if (!columnList.length) return 0\n const tableName = meta?.tableName\n if (!tableName) return 0\n const schema = meta?.schema\n const qualifiedTable = schema ? `\"${schema}\".\"${tableName}\"` : `\"${tableName}\"`\n const selectSql = `select ${columnList.map((c) => `\"${c}\"`).join(', ')} from ${qualifiedTable} where tenant_id = ? and organization_id is not distinct from ?`\n const rows = await conn.execute(selectSql, [scope.tenantId, scope.organizationId])\n const list = Array.isArray(rows) ? rows : []\n if (!list.length) return 0\n const dekAvailable = dryRun ? await hasExistingDek(scope.tenantId) : true\n // The scope pins one tenant, so the current DEK is the same for every row here.\n const currentDek = rotate && oldKms ? await encryptionService.getDek(scope.tenantId) : null\n let updated = 0\n for (const row of list) {\n const payload: Record<string, unknown> = {}\n for (const rule of fields) {\n const resolved = resolveProperty(meta, rule.field)\n const col = resolved?.columnName\n if (!col) continue\n const rawValue = row[col]\n // Rotate mode only touches values that look like an envelope (candidates to\n // re-key); encrypt mode only touches values that do not (plaintext to seal).\n // A shape-valid envelope encountered in encrypt mode is either already\n // correctly encrypted or sealed under a foreign DEK \u2014 either way, handing it\n // to encryptEntityPayload would either no-op or throw WRONG_KEY and abort the\n // whole run (#5951); mirror the filter the update path already applies below.\n if (isEncryptedPayloadShape(rawValue) ? !rotate : rotate) {\n continue\n }\n payload[rule.field] = rawValue\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n const hashCol = resolvedHash?.columnName\n if (hashCol) payload[rule.hashField] = row[hashCol]\n }\n }\n if (rotate && !Object.keys(payload).length) {\n continue\n }\n if (rotate && oldKms) {\n let oldDek = oldDekCache.get(scope.tenantId) ?? null\n if (!oldDekCache.has(scope.tenantId)) {\n oldDek = await oldKms.getTenantDek(scope.tenantId)\n oldDekCache.set(scope.tenantId, oldDek)\n }\n for (const rule of fields) {\n const value = payload[rule.field]\n if (typeof value !== 'string' || !isEncryptedPayloadShape(value)) continue\n const decrypted = decryptWithOldKey(value, oldDek)\n if (decrypted !== null) {\n payload[rule.field] = parseDecryptedFieldValue(decrypted)\n continue\n }\n // The old key did not open it. If the current key does, the row was already\n // rotated (a resumed run) \u2014 leave it in the payload, where the service's own\n // already-encrypted check skips it. Otherwise no key we hold can open it, and\n // handing it to the encrypt path would abort the whole batch (#5951). Drop the\n // field so this row is reported and skipped instead.\n if (currentDek && decryptWithAesGcm(value, currentDek.key) !== null) continue\n delete payload[rule.field]\n if (rule.hashField) delete payload[rule.hashField]\n unrecoverableRows.push({ entityId, field: rule.field, rowId: row[pk] })\n console.warn(\n `Skipping ${entityId}.${rule.field} for row ${row[pk]}: its ciphertext opens under neither --old-key nor the current tenant key. Re-run with the key that sealed it.`,\n )\n }\n }\n if (!dekAvailable) {\n // Nothing was encrypted because no key exists and this run refuses to\n // create one. Count the rows a real run would rewrite by applying the\n // same plaintext/ciphertext filters the update path uses below.\n const wouldChange = fields.some((rule) => {\n const col = resolveProperty(meta, rule.field)?.columnName\n if (!col) return false\n const value = row[col]\n if (value === null || value === undefined) return false\n return rotate ? isEncryptedPayloadShape(value) : !isEncryptedPayloadShape(value)\n })\n if (wouldChange) updated += 1\n continue\n }\n const encrypted = await encryptionService.encryptEntityPayload(\n entityId,\n payload,\n scope.tenantId,\n scope.organizationId,\n { createMissingDek: !dryRun },\n )\n const updates: Record<string, unknown> = {}\n for (const rule of fields) {\n const resolved = resolveProperty(meta, rule.field)\n const col = resolved?.columnName\n if (!col) continue\n const nextValue = (encrypted as any)[rule.field]\n if (nextValue !== undefined && nextValue !== row[col]) {\n if (!rotate && isEncryptedPayloadShape(row[col])) continue\n updates[col] = formatValueForColumn(resolved?.prop, nextValue)\n }\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n const hashCol = resolvedHash?.columnName\n const hashValue = (encrypted as any)[rule.hashField]\n if (hashCol && hashValue !== undefined && hashValue !== row[hashCol]) {\n updates[hashCol] = formatValueForColumn(resolvedHash?.prop, hashValue)\n }\n }\n }\n if (!Object.keys(updates).length) continue\n if (!dryRun) {\n const setSql = Object.keys(updates).map((col) => `\"${col}\" = ?`).join(', ')\n await conn.execute(\n `update ${qualifiedTable} set ${setSql} where \"${pk}\" = ?`,\n [...Object.values(updates), row[pk]],\n )\n }\n updated += 1\n }\n return updated\n }\n\n let total = 0\n for (const map of maps) {\n const mapMeta = resolveMapMeta(map, metaByEntityId, console.warn)\n if (!mapMeta) continue\n const { entityId, meta, fields, tenantId } = mapMeta\n const scopes = await resolveScopes(tenantId, map.organizationId ? String(map.organizationId) : null)\n for (const scope of scopes) {\n const updated = await processScope(entityId, meta, fields, scope)\n if (updated > 0) {\n console.log(\n `${dryRun ? '[dry-run] ' : ''}Encrypted ${updated} record(s) for ${entityId} org=${scope.organizationId ?? 'null'}`\n )\n }\n total += updated\n }\n }\n\n if (total > 0) {\n console.log(`Encrypted ${total} record(s) across mapped entities.`)\n } else {\n console.log('All mapped entity fields already encrypted for the selected scope.')\n }\n if (unrecoverableRows.length) {\n const maxListed = 20\n console.log(\n `\\n\u26A0\uFE0F ${unrecoverableRows.length} field(s) could not be rotated \u2014 their ciphertext opens under neither `\n + '--old-key nor the current tenant key, so they were left untouched. See '\n + '\"Key management\" in apps/docs/docs/architecture/data-encryption.mdx for how to resolve these manually.',\n )\n for (const row of unrecoverableRows.slice(0, maxListed)) {\n console.log(` - ${row.entityId}.${row.field} row=${String(row.rowId)}`)\n }\n if (unrecoverableRows.length > maxListed) {\n console.log(` ... and ${unrecoverableRows.length - maxListed} more`)\n }\n }\n },\n}\n\nconst decryptDatabase: ModuleCli = {\n command: 'decrypt-database',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantIdArg = (args.tenant as string) || (args.tenantId as string) || null\n const organizationIdArg = (args.org as string) || (args.organization as string) || (args.organizationId as string) || null\n const entityIdArg = (args.entity as string) || null\n const checkMode = Boolean(args.check)\n const dryRun = Boolean(args['dry-run'] || args.dry) || checkMode\n const deactivateMaps = Boolean(args['deactivate-maps'])\n const confirm = (args.confirm as string) || null\n const batchSize = Math.max(1, parseInt(String(args['batch-size'] || args.batchSize || '500'), 10) || 500)\n const sleepMs = Math.max(0, parseInt(String(args['sleep-ms'] || args.sleepMs || '0'), 10) || 0)\n const debug = Boolean(args.debug)\n\n if (!tenantIdArg) {\n console.error('--tenant <uuid> is required.')\n return\n }\n\n if (!checkMode) {\n if (!confirm) {\n console.error('--confirm <tenantUuid> is required (safety gate). Pass the exact tenant UUID to confirm the operation.')\n return\n }\n if (confirm !== tenantIdArg) {\n console.error(`--confirm value \"${confirm}\" does not match --tenant \"${tenantIdArg}\". Aborting.`)\n return\n }\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const conn: any = em?.getConnection?.()\n if (!conn || typeof conn.execute !== 'function') {\n console.error('Unable to access raw database connection; aborting.')\n return\n }\n\n if (!checkMode && !isTenantDataEncryptionEnabled()) {\n console.error('TENANT_DATA_ENCRYPTION is disabled; aborting. Data may already be decrypted.')\n return\n }\n\n const metaByEntityId = buildEntityMetaRegistry(em)\n\n const resolveDecryptScopes = async (\n tenantId: string,\n organizationId: string | null,\n ): Promise<Array<{ tenantId: string; organizationId: string | null }>> => {\n if (organizationId) return [{ tenantId, organizationId }]\n const rows = await conn.execute(\n `SELECT DISTINCT organization_id FROM encryption_maps WHERE tenant_id = ? AND deleted_at IS NULL`,\n [tenantId],\n )\n const orgIds = new Set<string | null>()\n for (const row of Array.isArray(rows) ? rows : []) {\n orgIds.add(row.organization_id ?? null)\n }\n orgIds.add(null)\n return Array.from(orgIds).map((orgId) => ({ tenantId, organizationId: orgId }))\n }\n\n const mapWhere: any = { tenantId: tenantIdArg, deletedAt: null, isActive: true }\n if (organizationIdArg) mapWhere.organizationId = organizationIdArg\n if (entityIdArg) mapWhere.entityId = entityIdArg\n const allMaps = await em.find(EncryptionMap, mapWhere)\n const systemScopedEntityIds = await getSystemScopedEntityIds()\n const maps = allMaps.filter((map: EncryptionMap) => {\n if (!systemScopedEntityIds.has(String(map.entityId))) return true\n console.warn(\n `Skipping ${map.entityId}: system-scoped entity. Its ciphertext is sealed under a system key that the tenant DEK cannot open, so this command leaves it untouched.`,\n )\n return false\n })\n\n if (!maps.length) {\n console.log('No active encryption maps found for the selected scope.')\n return\n }\n\n const kms = createKmsService()\n const dekCache = new Map<string, TenantDek | null>()\n const getDek = async (tenantId: string): Promise<TenantDek | null> => {\n if (dekCache.has(tenantId)) return dekCache.get(tenantId) ?? null\n const dek = await kms.getTenantDek(tenantId)\n dekCache.set(tenantId, dek)\n return dek\n }\n\n if (checkMode) {\n const envValue = process.env.TENANT_DATA_ENCRYPTION ?? '(not set)'\n console.log(`TENANT_DATA_ENCRYPTION = ${envValue}`)\n console.log(`Active EncryptionMap records for scope: ${maps.length}`)\n let encryptedCandidatesSampled = 0\n let malformedPayloadCountSampled = 0\n for (const map of maps) {\n const mapMeta = resolveMapMeta(map, metaByEntityId)\n if (!mapMeta) continue\n const { entityId, meta, fields, tenantId } = mapMeta\n const dek = await getDek(tenantId).catch(() => null)\n if (!dek) continue\n const scopes = await resolveDecryptScopes(tenantId, map.organizationId ? String(map.organizationId) : null)\n const pk = Array.isArray(meta?.primaryKeys) && meta.primaryKeys.length ? meta.primaryKeys[0] : 'id'\n const tableName = meta?.tableName\n if (!tableName) continue\n const schema = meta?.schema\n const qualifiedTable = schema ? `\"${schema}\".\"${tableName}\"` : `\"${tableName}\"`\n const fieldCols = fields.flatMap((f: any) => {\n const r = resolveProperty(meta, f.field)\n return r.columnName ? [r.columnName] : []\n })\n const colList = Array.from(new Set([pk, ...fieldCols]))\n for (const scope of scopes) {\n const sampleRows = await conn.execute(\n `SELECT ${colList.map((c: string) => `\"${c}\"`).join(', ')} FROM ${qualifiedTable} WHERE tenant_id = ? AND organization_id IS NOT DISTINCT FROM ? LIMIT 100`,\n [scope.tenantId, scope.organizationId],\n ).catch(() => [])\n for (const row of Array.isArray(sampleRows) ? sampleRows : []) {\n let rowHasEncrypted = false\n for (const fieldRule of fields) {\n const resolved = resolveProperty(meta, fieldRule.field)\n const col = resolved?.columnName\n if (!col) continue\n const rawValue = row[col]\n if (rawValue === null || rawValue === undefined) continue\n try {\n decryptWithAesGcmStrict(String(rawValue), dek.key)\n rowHasEncrypted = true\n } catch (e: any) {\n if (e instanceof TenantDataEncryptionError && e.code === TenantDataEncryptionErrorCode.MALFORMED_PAYLOAD) {\n malformedPayloadCountSampled++\n }\n }\n }\n if (rowHasEncrypted) encryptedCandidatesSampled++\n }\n }\n }\n console.log(`estimated encrypted candidates (sampled): ${encryptedCandidatesSampled}`)\n if (malformedPayloadCountSampled > 0) {\n console.warn(`\u26A0 malformed payloads (sampled): ${malformedPayloadCountSampled} \u2014 may indicate corruption`)\n } else {\n console.log(`malformed payloads (sampled): ${malformedPayloadCountSampled}`)\n }\n console.log('not a proof of absence \u2014 run full command + rerun --check to confirm')\n return\n }\n\n let totalRowsFetched = 0\n let totalRowsUpdated = 0\n let totalHashFieldsCleared = 0\n const totalHashFieldsSkipped = new Set<string>()\n let totalMalformedPayloadCount = 0\n const malformedByLocation = new Map<string, number>()\n let totalEntitiesProcessed = 0\n\n for (const map of maps) {\n const mapMeta = resolveMapMeta(map, metaByEntityId, console.warn)\n if (!mapMeta) continue\n const { entityId, meta, fields, tenantId } = mapMeta\n const dek = await getDek(tenantId)\n if (!dek) {\n console.warn(`No DEK available for tenant ${tenantId}; skipping ${entityId}.`)\n continue\n }\n const pk = Array.isArray(meta?.primaryKeys) && meta.primaryKeys.length ? meta.primaryKeys[0] : 'id'\n const tableName = meta?.tableName\n if (!tableName) {\n console.warn(`Skipping ${entityId}: table name not found.`)\n continue\n }\n const schema = meta?.schema\n const qualifiedTable = schema ? `\"${schema}\".\"${tableName}\"` : `\"${tableName}\"`\n const fieldCols = fields.flatMap((f: any) => {\n const r = resolveProperty(meta, f.field)\n return r.columnName ? [r.columnName] : []\n })\n const colList = Array.from(new Set([pk, ...fieldCols]))\n totalEntitiesProcessed++\n\n const scopes = await resolveDecryptScopes(tenantId, map.organizationId ? String(map.organizationId) : null)\n\n for (const scope of scopes) {\n let lastId: string | null = null\n let scopeMalformedCount = 0\n\n while (true) {\n let selectSql = `SELECT ${colList.map((c: string) => `\"${c}\"`).join(', ')} FROM ${qualifiedTable} WHERE tenant_id = ? AND organization_id IS NOT DISTINCT FROM ?`\n const selectParams: unknown[] = [scope.tenantId, scope.organizationId]\n if (lastId !== null) {\n selectSql += ` AND \"${pk}\" > ?`\n selectParams.push(lastId)\n }\n selectSql += ` ORDER BY \"${pk}\" LIMIT ?`\n selectParams.push(batchSize)\n\n const batchRows = await conn.execute(selectSql, selectParams)\n const batch = Array.isArray(batchRows) ? batchRows : []\n if (!batch.length) break\n\n lastId = String(batch[batch.length - 1]![pk])\n totalRowsFetched += batch.length\n\n const batchStart = Date.now()\n await conn.execute('BEGIN')\n let batchCommitted = false\n try {\n for (const row of batch) {\n const updates: Record<string, unknown> = {}\n let rowDecrypted = false\n\n for (const fieldRule of fields) {\n const resolved = resolveProperty(meta, fieldRule.field)\n const col = resolved?.columnName\n if (!col) continue\n const rawValue = row[col]\n if (rawValue === null || rawValue === undefined) continue\n try {\n const decrypted = decryptWithAesGcmStrict(String(rawValue), dek.key)\n let valueToWrite: string\n try {\n const parsed = JSON.parse(decrypted)\n valueToWrite = typeof parsed === 'string' ? parsed : decrypted\n } catch {\n valueToWrite = decrypted\n }\n updates[col] = valueToWrite\n rowDecrypted = true\n } catch (e: any) {\n if (e instanceof TenantDataEncryptionError) {\n if (e.code === TenantDataEncryptionErrorCode.AUTH_FAILED) {\n // Value is plaintext \u2014 skip silently\n } else if (e.code === TenantDataEncryptionErrorCode.MALFORMED_PAYLOAD) {\n scopeMalformedCount++\n const locationKey = `${tableName}:${col}`\n malformedByLocation.set(locationKey, (malformedByLocation.get(locationKey) ?? 0) + 1)\n console.warn(`\u26A0 MALFORMED_PAYLOAD for ${entityId} field \"${col}\" row ${row[pk]}; skipping field.`)\n } else {\n throw e\n }\n } else {\n throw e\n }\n }\n }\n\n if (rowDecrypted) {\n for (const fieldRule of fields) {\n if (!fieldRule.hashField) continue\n const resolvedHash = resolveProperty(meta, fieldRule.hashField)\n const hashCol = resolvedHash?.columnName\n if (!hashCol) {\n const skippedKey = `${tableName}:${fieldRule.hashField}`\n if (!totalHashFieldsSkipped.has(skippedKey)) {\n console.warn(`\u26A0 Hash column \"${fieldRule.hashField}\" not found in metadata for ${entityId}; skipping.`)\n totalHashFieldsSkipped.add(skippedKey)\n }\n continue\n }\n updates[hashCol] = null\n totalHashFieldsCleared++\n }\n }\n\n if (Object.keys(updates).length > 0) {\n if (!dryRun) {\n const setSql = Object.keys(updates).map((col) => `\"${col}\" = ?`).join(', ')\n await conn.execute(\n `UPDATE ${qualifiedTable} SET ${setSql} WHERE \"${pk}\" = ?`,\n [...Object.values(updates), row[pk]],\n )\n }\n totalRowsUpdated++\n }\n }\n await conn.execute('COMMIT')\n batchCommitted = true\n } catch (fatalErr: any) {\n if (!batchCommitted) {\n try { await conn.execute('ROLLBACK') } catch {}\n }\n console.error(`Fatal error during batch processing for ${entityId}: ${(fatalErr as Error)?.message || String(fatalErr)}`)\n throw fatalErr\n }\n\n const batchDurationMs = Date.now() - batchStart\n if (debug) {\n console.log(\n `[debug] Batch ${entityId} org=${scope.organizationId ?? 'null'}: ${batch.length} rows in ${batchDurationMs}ms, scopeMalformed=${scopeMalformedCount}`,\n )\n if (batchDurationMs > 30_000) {\n console.warn('\u26A0 Batch took >30s; consider reducing --batch-size.')\n }\n }\n if (sleepMs > 0) {\n await new Promise<void>((r) => setTimeout(r, sleepMs))\n }\n }\n\n totalMalformedPayloadCount += scopeMalformedCount\n }\n }\n\n if (deactivateMaps && !dryRun) {\n let deactivateSql = `UPDATE encryption_maps SET is_active = false, deleted_at = now() WHERE tenant_id = ? AND deleted_at IS NULL`\n const deactivateParams: unknown[] = [tenantIdArg]\n if (organizationIdArg) {\n deactivateSql += ` AND (organization_id = ? OR organization_id IS NULL)`\n deactivateParams.push(organizationIdArg)\n }\n if (entityIdArg) {\n deactivateSql += ` AND entity_id = ?`\n deactivateParams.push(entityIdArg)\n }\n await conn.execute(deactivateSql, deactivateParams)\n console.warn('\u26A0 Restart all application replicas \u2014 in-process map caches may still be active.')\n if (isTenantDataEncryptionEnabled()) {\n console.warn('\u26A0 Env TENANT_DATA_ENCRYPTION is still true \u2014 new writes will be re-encrypted until env is updated and replicas restarted.')\n }\n }\n\n if (deactivateMaps && dryRun) {\n console.log(`[dry-run] Would deactivate ${maps.length} EncryptionMap record(s).`)\n }\n\n const prefix = dryRun ? '[dry-run] ' : ''\n console.log(`\\n${prefix}Decryption summary:`)\n console.log(` Rows fetched: ${totalRowsFetched}`)\n console.log(` Rows updated: ${totalRowsUpdated}`)\n console.log(` Entities processed: ${totalEntitiesProcessed}`)\n console.log(` Hash fields cleared: ${totalHashFieldsCleared}`)\n if (totalHashFieldsSkipped.size > 0) {\n console.log(` Hash fields skipped (missing columns): ${Array.from(totalHashFieldsSkipped).join(', ')}`)\n }\n if (totalMalformedPayloadCount > 0) {\n console.warn(\n ` \u26A0 ${totalMalformedPayloadCount} field value(s) returned MALFORMED_PAYLOAD and were skipped; these may be corrupted ciphertexts. Investigate before assuming decryption is complete.`,\n )\n if (debug && malformedByLocation.size > 0) {\n const top = Array.from(malformedByLocation.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n console.log(' Top malformed locations:')\n for (const [loc, count] of top) {\n console.log(` ${loc}: ${count}`)\n }\n }\n }\n\n if (!dryRun) {\n console.log(`\\n\u2705 Decryption complete. Required next steps:`)\n console.log(` 1. Set TENANT_DATA_ENCRYPTION=false in your environment / secrets`)\n console.log(` 2. Restart all application replicas`)\n console.log(` 3. Run: mercato query_index reindex --tenant ${tenantIdArg} \u2190 run after env flip + restart; search/filter degraded until this completes`)\n console.log(` 4. Run: mercato entities decrypt-database --tenant ${tenantIdArg} --check to confirm no encrypted values remain`)\n console.log(` NOTE: if the run was long and concurrent inserts occurred, run again before step 4 \u2014 it is idempotent.`)\n }\n },\n}\n\n/**\n * Encrypt rows that a **system-scoped** encryption map covers but that were written\n * before the map existed.\n *\n * `rotate-encryption-key` cannot reach them: it walks the `encryption_maps` table and\n * skips every row without a `tenant_id`, while system-scoped maps are declared in module\n * code (`defaultEncryptionMaps` with `keyScope: 'system'`) and cover pre-tenant records\n * that have no tenant at all \u2014 onboarding requests being the first such entity. Without a\n * backfill those historical rows stay plaintext forever, since only the write path\n * encrypts.\n *\n * The command is forward-only (plaintext \u2192 ciphertext), never accepts an old key and never\n * decrypts, so it cannot re-encrypt data under the wrong key. It is idempotent: values that\n * already decrypt under the current system DEK are left untouched by\n * `encryptEntityPayload`, so a partial run can simply be repeated.\n */\nconst backfillSystemEncryption: ModuleCli = {\n command: 'backfill-system-encryption',\n async run(rest) {\n const args = parseArgs(rest)\n const entityIdArg = (args.entity as string) || null\n const dryRun = Boolean(args['dry-run'] || args.dry)\n const batchSize = Math.max(1, parseInt(String(args['batch-size'] || args.batchSize || '500'), 10) || 500)\n const debug = Boolean(args.debug)\n\n if (!isTenantDataEncryptionEnabled()) {\n console.error('TENANT_DATA_ENCRYPTION is disabled; aborting. Enable encryption before backfilling.')\n return\n }\n\n const systemMaps = getDefaultEncryptionMaps(await resolveEncryptionMapModules()).filter((map) => map.keyScope === 'system')\n if (!systemMaps.length) {\n console.log('No system-scoped encryption maps are declared by the installed modules. Nothing to backfill.')\n return\n }\n const selectedMaps = entityIdArg ? systemMaps.filter((map) => map.entityId === entityIdArg) : systemMaps\n if (!selectedMaps.length) {\n console.error(`No system-scoped encryption map declared for entity \"${entityIdArg}\".`)\n console.error(`Known system-scoped entities: ${systemMaps.map((map) => map.entityId).join(', ')}`)\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const conn: any = em?.getConnection?.()\n if (!conn || typeof conn.execute !== 'function') {\n console.error('Unable to access raw database connection; aborting.')\n return\n }\n\n const encryptionService = new TenantDataEncryptionService(em as any, {\n kms: createKmsService(),\n defaultEncryptionMaps: systemMaps,\n })\n if (!encryptionService.isEnabled()) {\n console.error('Encryption service is not enabled (KMS unhealthy). Aborting without touching any row.')\n return\n }\n\n const metaByEntityId = buildEntityMetaRegistry(em)\n const prefix = dryRun ? '[dry-run] ' : ''\n let totalRowsScanned = 0\n let totalRowsUpdated = 0\n let totalRowsUnchanged = 0\n\n for (const map of selectedMaps) {\n const entityId = map.entityId\n const meta = metaByEntityId.get(entityId)\n if (!meta) {\n console.warn(`Skipping ${entityId}: entity metadata not found (is the owning module enabled?).`)\n continue\n }\n const tableName = meta?.tableName\n if (!tableName) {\n console.warn(`Skipping ${entityId}: entity has no table name.`)\n continue\n }\n const primaryKey = Array.isArray(meta?.primaryKeys) && meta.primaryKeys.length ? meta.primaryKeys[0] : 'id'\n const { columnName: primaryKeyColumn } = resolveProperty(meta, primaryKey)\n const pkColumn = primaryKeyColumn ?? primaryKey\n const qualifiedTable = meta?.schema ? `\"${meta.schema}\".\"${tableName}\"` : `\"${tableName}\"`\n\n const columns = new Set<string>([pkColumn])\n for (const rule of map.fields) {\n const resolved = resolveProperty(meta, rule.field)\n if (resolved.columnName) columns.add(resolved.columnName)\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n if (resolvedHash.columnName) columns.add(resolvedHash.columnName)\n }\n }\n const columnList = Array.from(columns)\n const selectList = columnList.map((column) => `\"${column}\"`).join(', ')\n\n // Same dry-run guarantee as rotate-encryption-key: previewing must not make\n // the KMS provision this entity's system DEK as a side effect (#5950).\n const systemDekAvailable = dryRun\n ? Boolean(await encryptionService.getDek(resolveEncryptionKeyId(entityId, 'system', null)))\n : true\n if (!systemDekAvailable) {\n console.warn(\n `[dry-run] ${entityId} has no system data-encryption key yet. Reporting the rows a real run would encrypt; no key material was provisioned.`,\n )\n }\n\n let cursor: unknown = null\n let entityRowsScanned = 0\n let entityRowsUpdated = 0\n let entityRowsUnchanged = 0\n\n for (;;) {\n const selectSql = cursor === null\n ? `select ${selectList} from ${qualifiedTable} order by \"${pkColumn}\" asc limit ?`\n : `select ${selectList} from ${qualifiedTable} where \"${pkColumn}\" > ? order by \"${pkColumn}\" asc limit ?`\n const params = cursor === null ? [batchSize] : [cursor, batchSize]\n const rows = await conn.execute(selectSql, params)\n const list = Array.isArray(rows) ? rows : []\n if (!list.length) break\n\n for (const row of list) {\n entityRowsScanned += 1\n cursor = row[pkColumn]\n const payload: Record<string, unknown> = {}\n let hasPlaintext = false\n for (const rule of map.fields) {\n const resolved = resolveProperty(meta, rule.field)\n if (!resolved.columnName) continue\n const rawValue = row[resolved.columnName]\n // Only plaintext belongs in the payload. An already-encrypted column produced no\n // update anyway (the service skips what decrypts under the current key), but if the\n // system key ever changed, passing its ciphertext through would abort the whole\n // backfill rather than let the row's genuinely-plaintext fields through (#5951).\n if (rawValue !== null && rawValue !== undefined && isEncryptedPayloadShape(rawValue)) continue\n payload[rule.field] = rawValue\n if (rawValue !== null && rawValue !== undefined) hasPlaintext = true\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n if (resolvedHash.columnName) payload[rule.hashField] = row[resolvedHash.columnName]\n }\n }\n if (!hasPlaintext) continue\n if (!systemDekAvailable) {\n // `hasPlaintext` already means a real run would rewrite this row.\n entityRowsUpdated += 1\n continue\n }\n\n const encrypted = await encryptionService.encryptEntityPayload(\n entityId,\n payload,\n null,\n null,\n { createMissingDek: !dryRun },\n )\n const updates: Record<string, unknown> = {}\n for (const rule of map.fields) {\n const resolved = resolveProperty(meta, rule.field)\n if (resolved.columnName) {\n const nextValue = encrypted[rule.field]\n if (nextValue !== undefined && nextValue !== row[resolved.columnName]) {\n updates[resolved.columnName] = formatValueForColumn(resolved.prop, nextValue)\n }\n }\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n const nextHash = encrypted[rule.hashField]\n if (resolvedHash.columnName && nextHash !== undefined && nextHash !== row[resolvedHash.columnName]) {\n updates[resolvedHash.columnName] = formatValueForColumn(resolvedHash.prop, nextHash)\n }\n }\n }\n if (!Object.keys(updates).length) {\n entityRowsUnchanged += 1\n if (debug) console.warn(`[backfill-system-encryption] ${entityId} ${String(row[pkColumn])}: plaintext left unchanged`)\n continue\n }\n if (!dryRun) {\n const setSql = Object.keys(updates).map((column) => `\"${column}\" = ?`).join(', ')\n await conn.execute(\n `update ${qualifiedTable} set ${setSql} where \"${pkColumn}\" = ?`,\n [...Object.values(updates), row[pkColumn]],\n )\n }\n entityRowsUpdated += 1\n }\n\n if (list.length < batchSize) break\n }\n\n totalRowsScanned += entityRowsScanned\n totalRowsUpdated += entityRowsUpdated\n totalRowsUnchanged += entityRowsUnchanged\n console.log(`${prefix}${entityId}: scanned ${entityRowsScanned}, encrypted ${entityRowsUpdated}`)\n }\n\n console.log(`\\n${prefix}Backfill summary:`)\n console.log(` Rows scanned: ${totalRowsScanned}`)\n console.log(` Rows encrypted: ${totalRowsUpdated}`)\n if (totalRowsUnchanged > 0) {\n console.warn(\n ` \u26A0 ${totalRowsUnchanged} row(s) held plaintext that could not be encrypted \u2014 the system DEK was unavailable. Verify the KMS/fallback key, then re-run (use --debug to list the rows).`,\n )\n }\n if (!dryRun && totalRowsUpdated > 0) {\n console.log('\\n\u2705 Backfill complete. Re-run with --dry-run to confirm no plaintext rows remain.')\n }\n },\n}\n\n// Keep default export stable (install first for help listing)\nexport default [seedDefs, reinstallDefs, addField, seedEncryptionMaps, rotateEncryptionKey, decryptDatabase, backfillSystemEncryption]\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["import { getCliModules, getDefaultEncryptionMaps, type Module, type ModuleCli } from '@open-mercato/shared/modules/registry'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport type { CacheStrategy } from '@open-mercato/cache/types'\nimport { CustomEntity, CustomFieldDef, EncryptionMap } from './data/entities'\nimport {\n upsertCanonicalEncryptionMap,\n upsertEncryptionMapSpecs,\n upsertEncryptionMaps,\n invalidateEncryptionMapScopes,\n} from './lib/encryption-maps'\nimport {\n installCustomEntitiesFromModules,\n getAggregatedCustomEntityConfigs,\n} from './lib/install-from-ce'\nimport readline from 'node:readline/promises'\nimport { stdin as input, stdout as output } from 'node:process'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\nimport { parseBooleanToken } from '@open-mercato/shared/lib/boolean'\nimport { parseCommaSeparatedList } from '@open-mercato/shared/lib/string'\nimport { createKmsService, type KmsService, type TenantDek } from '@open-mercato/shared/lib/encryption/kms'\nimport {\n decryptWithAesGcm,\n decryptWithAesGcmStrict,\n isEncryptedPayloadShape,\n TenantDataEncryptionError,\n TenantDataEncryptionErrorCode,\n} from '@open-mercato/shared/lib/encryption/aes'\nimport {\n TenantDataEncryptionService,\n parseDecryptedFieldValue,\n resolveEncryptionKeyId,\n} from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { resolveEntityIdFromMetadata } from '@open-mercato/shared/lib/encryption/entityIds'\nimport { listEntityMetadata } from '@open-mercato/shared/lib/db/entityMetadata'\nimport { rotateCustomEntityDocuments } from './lib/rotate-custom-entity-documents'\nimport { Organization } from '../directory/data/entities'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport crypto from 'node:crypto'\n\nfunction parseArgs(rest: string[]) {\n const args: Record<string, string | boolean> = {}\n for (let i = 0; i < rest.length; i++) {\n const a = rest[i]\n if (!a) continue\n if (a.startsWith('--')) {\n const [k, v] = a.replace(/^--/, '').split('=')\n if (v !== undefined) args[k] = v\n else if (rest[i + 1] && !rest[i + 1]!.startsWith('--')) { args[k] = rest[i + 1]!; i++ }\n else args[k] = true\n }\n }\n return args\n}\n\nconst seedDefs: ModuleCli = {\n command: 'install',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantIdArg = (args.tenant as string) || (args.tenantId as string)\n const globalOnly = Boolean(args.global)\n const dry = Boolean(args['dry-run'] || args.dry)\n const force = Boolean(args.force)\n const includeGlobal = args['no-global'] ? false : true\n\n if (globalOnly && includeGlobal === false) {\n console.error('Cannot combine --global with --no-global.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n let cache: CacheStrategy | null = null\n try { cache = resolve('cache') as CacheStrategy } catch {}\n\n const tenantIds = tenantIdArg\n ? [tenantIdArg]\n : (globalOnly ? [] : undefined)\n\n const logger = (message: string) => {\n const prefix = dry ? '[dry-run] ' : ''\n console.log(`${prefix}${message}`)\n }\n\n const result = await installCustomEntitiesFromModules(em, cache, {\n tenantIds,\n includeGlobal,\n dryRun: dry,\n force,\n logger,\n })\n const label = dry ? 'Dry-run' : 'Sync'\n console.log(`\u2705 ${label} complete: processed=${result.processed}, updated=${result.synchronized}, fieldsChanged=${result.fieldChanges}, skipped=${result.skipped}`)\n },\n}\n\n// Reinstall: remove existing definitions for target scope and re-seed from modules\nconst reinstallDefs: ModuleCli = {\n command: 'reinstall',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantIdArg = (args.tenant as string) || (args.tenantId as string)\n const globalOnly = Boolean(args.global)\n const dry = Boolean(args['dry-run'] || args.dry)\n const includeGlobal = globalOnly ? true : (args['no-global'] ? false : true)\n\n if (globalOnly && includeGlobal === false) {\n console.error('Cannot combine --global with --no-global.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n let cache: CacheStrategy | null = null\n try { cache = resolve('cache') as CacheStrategy } catch {}\n\n const tenantIds = tenantIdArg\n ? [tenantIdArg]\n : (globalOnly ? [] : undefined)\n\n const aggregates = getAggregatedCustomEntityConfigs()\n const relevant = aggregates.filter((entry) => (globalOnly ? entry.spec?.global === true : true))\n if (!relevant.length) {\n console.log('No custom entities or fields discovered. Nothing to reinstall.')\n return\n }\n const entityIds = Array.from(new Set(relevant.map((entry) => entry.entityId)))\n if (!entityIds.length) {\n console.log('No entity ids discovered. Nothing to reinstall.')\n return\n }\n\n const logger = (message: string) => {\n const prefix = dry ? '[dry-run] ' : ''\n console.log(`${prefix}${message}`)\n }\n\n if (dry) {\n console.log('Dry-run: would remove existing custom entity definitions before reinstall.')\n } else {\n const fieldWhere: any = { entityId: { $in: entityIds } }\n if (tenantIds !== undefined) {\n if (tenantIds.length === 0) fieldWhere.tenantId = null\n else fieldWhere.tenantId = { $in: tenantIds }\n }\n const removedFields = await em.nativeDelete(CustomFieldDef, fieldWhere)\n\n const entityWhere: any = { entityId: { $in: entityIds } }\n if (tenantIds !== undefined) {\n if (tenantIds.length === 0) entityWhere.tenantId = null\n else entityWhere.tenantId = { $in: tenantIds }\n }\n const removedEntities = await em.nativeDelete(CustomEntity, entityWhere)\n\n if (cache && entityIds.length) {\n try {\n await cache.deleteByTags(entityIds.map((id) => `custom-entity:${id}`))\n } catch {}\n }\n console.log(`Cleared definitions: fields=${removedFields}, entities=${removedEntities}`)\n }\n\n const result = await installCustomEntitiesFromModules(em, cache, {\n tenantIds,\n includeGlobal,\n dryRun: dry,\n force: true,\n logger,\n })\n const label = dry ? 'Dry-run' : 'Reinstall'\n console.log(`\u2705 ${label} complete: processed=${result.processed}, updated=${result.synchronized}, fieldsChanged=${result.fieldChanges}, skipped=${result.skipped}`)\n },\n}\n\n// Interactive: add a single custom field definition\nconst addField: ModuleCli = {\n command: 'add-field',\n async run(rest) {\n const args = parseArgs(rest)\n const rl = readline.createInterface({ input, output })\n const ask = async (q: string, d?: string) => {\n const a = (await rl.question(d ? `${q} [${d}]: ` : `${q}: `)).trim()\n return a || (d ?? '')\n }\n const askBool = async (q: string, d = false) => {\n const a = (await ask(q, d ? 'y' : 'n')).toLowerCase()\n return parseBooleanToken(a) === true\n }\n\n try {\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n\n const entityId = (args.entity as string) || (args.e as string) || await ask('Entity ID (e.g., example:todo)')\n const isGlobal = args.global ? true : await askBool('Global (no organization)?', false)\n const orgId = isGlobal ? null : ((args.org as string) || (args.organizationId as string) || await ask('Organization ID'))\n const tenantId = isGlobal ? null : ((args.tenant as string) || (args.tenantId as string) || await ask('Tenant ID'))\n const key = (args.key as string) || await ask('Field key (snake_case)')\n let kind = (args.kind as string) || await ask(\"Kind (text|multiline|integer|float|boolean|select|currency|relation|attachment)\", 'text')\n kind = kind.toLowerCase()\n if (!['text','multiline','integer','float','boolean','select','currency','relation','attachment'].includes(kind)) throw new Error('Invalid kind')\n const label = (args.label as string) || (await ask('Label', key))\n const description = (args.description as string) || ''\n const required = args.required !== undefined ? Boolean(args.required) : await askBool('Required?', false)\n const multi = args.multi !== undefined ? Boolean(args.multi) : await askBool('Allow multiple?', false)\n let options: string[] | undefined\n if (kind === 'select') {\n const raw = (args.options as string) || await ask('Options (comma-separated)', 'low,medium,high')\n options = parseCommaSeparatedList(raw)\n }\n let defaultValue: any = undefined\n const defRaw = (args.default as string) ?? (args.defaultValue as string)\n const needDefault = defRaw !== undefined ? defRaw : await ask('Default value (leave empty for none)', '')\n if (needDefault !== '') {\n switch (kind) {\n case 'integer': defaultValue = Number(needDefault); break\n case 'float': defaultValue = Number(needDefault); break\n case 'boolean': defaultValue = parseBooleanToken(String(needDefault)) === true; break\n default: defaultValue = String(needDefault)\n }\n }\n const filterable = args.filterable !== undefined ? Boolean(args.filterable) : await askBool('Filterable?', true)\n const listVisible = args.listVisible !== undefined ? Boolean(args.listVisible) : await askBool('Visible in list?', true)\n const formEditable = args.formEditable !== undefined ? Boolean(args.formEditable) : await askBool('Editable in forms?', true)\n const indexed = args.indexed !== undefined ? Boolean(args.indexed) : await askBool('Indexed?', false)\n\n const where = { entityId, organizationId: orgId, tenantId: tenantId, key }\n const existing = await em.findOne(CustomFieldDef, where)\n const configJson: any = {}\n if (options) configJson.options = options\n if (defaultValue !== undefined) configJson.defaultValue = defaultValue\n if (required !== undefined) configJson.required = required\n if (multi !== undefined) configJson.multi = multi\n if (filterable !== undefined) configJson.filterable = filterable\n if (indexed !== undefined) configJson.indexed = indexed\n if (listVisible !== undefined) configJson.listVisible = listVisible\n if (formEditable !== undefined) configJson.formEditable = formEditable\n if (label !== undefined) configJson.label = label\n if (description !== undefined) configJson.description = description\n\n if (!existing) {\n await em.persist(em.create(CustomFieldDef, {\n entityId,\n organizationId: orgId,\n tenantId: tenantId,\n key,\n kind,\n configJson,\n isActive: true,\n })).flush()\n console.log(`Created custom field: ${entityId}.${key} (${kind})${orgId == null ? ' [global]' : ` [org=${orgId}, tenant=${tenantId}]`}`)\n } else {\n existing.kind = kind as any\n existing.configJson = configJson\n existing.isActive = true\n await em.flush()\n console.log(`Updated custom field: ${entityId}.${key} (${kind})${orgId == null ? ' [global]' : ` [org=${orgId}, tenant=${tenantId}]`}`)\n }\n } catch (e: any) {\n console.error('Failed:', e?.message || e)\n } finally {\n await rl.close()\n }\n },\n}\n\n// Re-exported for backward compatibility: callers importing these from `./cli` keep working.\n// `upsertEncryptionMapSpecs`/`upsertEncryptionMaps`/`invalidateEncryptionMapScopes` live in\n// `./lib/encryption-maps` (kept lean so the subscriber that seeds a new organization's maps does\n// not pull in this whole CLI module's dependency graph). `resolveEncryptionMapModules` stays\n// defined here: it reads `getCliModules()`, and `cli-registry-boundary.test.ts` in\n// @open-mercato/shared only allows that from `packages/cli/**` or a file literally named\n// `cli.ts` \u2014 a lib file could not host it.\nexport { upsertEncryptionMapSpecs, upsertEncryptionMaps, invalidateEncryptionMapScopes }\n\nasync function resolveEncryptionMapModules(): Promise<Module[]> {\n const cliModules = getCliModules()\n if (cliModules.length > 0) return cliModules\n try {\n const { getModules } = await import('@open-mercato/shared/lib/modules/registry')\n return getModules()\n } catch {\n return []\n }\n}\n\n/**\n * Entity ids whose encryption map is declared in module code with `keyScope: 'system'`.\n *\n * Their ciphertext is sealed under a `system:<entityId>` DEK that has no tenant, so every\n * tenant-scoped command here MUST leave them alone: re-wrapping such a value under a tenant\n * DEK produces a payload runtime decryption can never read again. `backfill-system-encryption`\n * is the one command that handles them.\n */\nasync function getSystemScopedEntityIds(): Promise<Set<string>> {\n return new Set(\n getDefaultEncryptionMaps(await resolveEncryptionMapModules())\n .filter((map) => map.keyScope === 'system')\n .map((map) => map.entityId),\n )\n}\n\nconst seedEncryptionMaps: ModuleCli = {\n command: 'seed-encryption',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantId = (args.tenant as string) || (args.tenantId as string)\n const organizationId = (args.org as string) || (args.organization as string) || (args.organizationId as string) || null\n\n if (!tenantId) {\n console.error('tenant id is required (use --tenant <uuid>)')\n return\n }\n if (!isTenantDataEncryptionEnabled()) {\n console.warn('TENANT_DATA_ENCRYPTION is disabled; skipping encryption map seeding.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as EntityManager\n const logger = (msg: string) => console.log(msg)\n const encryptionService = resolve('tenantEncryptionService') as Pick<TenantDataEncryptionService, 'invalidateMap'>\n await upsertEncryptionMaps(em, tenantId, organizationId, await resolveEncryptionMapModules(), logger, async (scope) => {\n await invalidateEncryptionMapScopes(encryptionService, [scope])\n })\n console.log('\u2705 Encryption maps seeded')\n },\n}\n\nfunction normalizeKeyInput(value: string): string {\n return value.trim().replace(/(?:^['\"]|['\"]$)/g, '')\n}\n\nclass DerivedKeyKmsService implements KmsService {\n private root: Buffer\n constructor(secret: string) {\n this.root = crypto.createHash('sha256').update(normalizeKeyInput(secret)).digest()\n }\n\n isHealthy(): boolean {\n return true\n }\n\n private deriveKey(tenantId: string): string {\n const iterations = 310_000\n const keyLength = 32\n const derived = crypto.pbkdf2Sync(this.root, tenantId, iterations, keyLength, 'sha512')\n return derived.toString('base64')\n }\n\n async getTenantDek(tenantId: string): Promise<TenantDek | null> {\n if (!tenantId) return null\n return { tenantId, key: this.deriveKey(tenantId), fetchedAt: Date.now() }\n }\n\n async createTenantDek(tenantId: string): Promise<TenantDek | null> {\n return this.getTenantDek(tenantId)\n }\n}\n\nfunction fingerprintDek(dek: TenantDek | null): string | null {\n if (!dek?.key) return null\n return crypto.createHash('sha256').update(dek.key).digest('hex').slice(0, 12)\n}\n\nfunction decryptWithOldKey(\n payload: string,\n dek: TenantDek | null,\n): string | null {\n if (!dek?.key) return null\n return decryptWithAesGcm(payload, dek.key)\n}\n\nfunction resolveProperty(meta: any, field: string): { columnName: string | null; prop: any | null } {\n if (!meta?.properties) return { columnName: null, prop: null }\n const candidates = [\n field,\n field.replace(/_([a-z])/g, (_, c) => c.toUpperCase()),\n field.replace(/([A-Z])/g, '_$1').toLowerCase(),\n ]\n for (const candidate of candidates) {\n const prop = meta.properties[candidate]\n const fieldName =\n prop?.fieldName ??\n (Array.isArray(prop?.fieldNames) && prop.fieldNames.length ? prop.fieldNames[0] : undefined)\n if (typeof fieldName === 'string' && fieldName.length) return { columnName: fieldName, prop }\n if (prop?.name) return { columnName: prop.name, prop }\n }\n return { columnName: null, prop: null }\n}\n\nfunction buildEntityMetaRegistry(em: any): Map<string, any> {\n const allMeta = listEntityMetadata(em)\n const metaByEntityId = new Map<string, any>()\n for (const meta of allMeta) {\n const resolved = resolveEntityIdFromMetadata(meta)\n if (resolved) metaByEntityId.set(resolved, meta)\n }\n return metaByEntityId\n}\n\ninterface EncryptionMapMeta {\n entityId: string\n meta: any\n fields: Array<{ field: string; hashField?: string | null }>\n tenantId: string\n}\n\nfunction resolveMapMeta(\n map: EncryptionMap,\n metaByEntityId: Map<string, any>,\n warn: (msg: string) => void = () => {},\n): EncryptionMapMeta | null {\n const entityId = String(map.entityId)\n const meta = metaByEntityId.get(entityId)\n if (!meta) {\n warn(`Skipping ${entityId}: metadata not found.`)\n return null\n }\n const fields = Array.isArray(map.fieldsJson) ? map.fieldsJson : []\n if (!fields.length) return null\n const tenantId = map.tenantId ? String(map.tenantId) : null\n if (!tenantId) return null\n return { entityId, meta, fields, tenantId }\n}\n\nfunction formatValueForColumn(prop: any, value: unknown): unknown {\n if (value === null || value === undefined) return value\n const types = Array.isArray(prop?.columnTypes) ? prop.columnTypes : []\n const type = String(prop?.type ?? '').toLowerCase()\n const isJson = types.some((entry: string) => entry.toLowerCase().includes('json')) || type === 'json' || type === 'jsonb'\n if (!isJson) return value\n return JSON.stringify(value)\n}\n\nconst rotateEncryptionKey: ModuleCli = {\n command: 'rotate-encryption-key',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantIdArg = (args.tenant as string) || (args.tenantId as string) || null\n const organizationIdArg = (args.org as string) || (args.organization as string) || (args.organizationId as string) || null\n const oldKey = (args['old-key'] as string) || (args.oldKey as string) || null\n const dryRun = Boolean(args['dry-run'] || args.dry)\n const debug = Boolean(args.debug)\n const rotate = Boolean(oldKey)\n if (rotate && !tenantIdArg) {\n console.warn(\n '\u26A0\uFE0F Rotating with --old-key across all tenants. A single old key should normally target one tenant; consider --tenant.',\n )\n }\n if (!isTenantDataEncryptionEnabled()) {\n console.error('TENANT_DATA_ENCRYPTION is disabled; aborting.')\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const conn: any = em?.getConnection?.()\n if (!conn || typeof conn.execute !== 'function') {\n console.error('Unable to access raw connection; aborting.')\n return\n }\n\n const encryptionService = new TenantDataEncryptionService(em as any, { kms: createKmsService() })\n const oldKms = rotate && oldKey ? new DerivedKeyKmsService(oldKey) : null\n if (!encryptionService.isEnabled()) {\n console.error('Encryption service is not enabled (KMS unhealthy or no DEK). Aborting.')\n return\n }\n\n if (debug) {\n console.log('[rotate-encryption-key]', {\n hasOldKey: Boolean(oldKey),\n rotate,\n tenantId: tenantIdArg ?? null,\n organizationId: organizationIdArg ?? null,\n })\n if (tenantIdArg) {\n const [oldDek, newDek] = await Promise.all([\n oldKms?.getTenantDek(tenantIdArg) ?? Promise.resolve(null),\n encryptionService.getDek(tenantIdArg),\n ])\n console.log('[rotate-encryption-key] dek fingerprints', {\n oldKey: fingerprintDek(oldDek),\n currentKey: fingerprintDek(newDek),\n })\n } else {\n console.log('[rotate-encryption-key] dek fingerprints skipped (no tenantId)')\n }\n }\n\n const metaByEntityId = buildEntityMetaRegistry(em)\n\n const where: any = { deletedAt: null }\n if (tenantIdArg) where.tenantId = tenantIdArg\n if (organizationIdArg) where.organizationId = organizationIdArg\n const allMaps = await em.find(EncryptionMap, where)\n const systemScopedEntityIds = await getSystemScopedEntityIds()\n const maps = allMaps.filter((map: EncryptionMap) => {\n if (!systemScopedEntityIds.has(String(map.entityId))) return true\n console.warn(\n `Skipping ${map.entityId}: system-scoped entity. Its ciphertext is sealed under a system key and cannot be rotated with a tenant key \u2014 use \"mercato entities backfill-system-encryption\" instead.`,\n )\n return false\n })\n const customDocuments = await rotateCustomEntityDocuments(em, {\n encryptionService,\n oldKms,\n tenantId: tenantIdArg,\n organizationId: organizationIdArg,\n dryRun,\n })\n if (customDocuments > 0) {\n console.log(`${dryRun ? '[dry-run] ' : ''}Encrypted ${customDocuments} custom entity document(s).`)\n }\n if (!maps.length) {\n if (!customDocuments) console.log('No encryption maps found for the selected scope.')\n return\n }\n\n const resolveScopes = async (tenantId: string, organizationId: string | null) => {\n if (organizationId) return [{ tenantId, organizationId }]\n const orgs = await em.find(Organization, { tenant: tenantId })\n const scopes = orgs.map((org: Organization) => ({\n tenantId,\n organizationId: String(org.id),\n }))\n scopes.push({ tenantId, organizationId: null })\n return scopes\n }\n\n const oldDekCache = new Map<string, TenantDek | null>()\n // Rows this run could not rotate because their ciphertext opens under neither\n // --old-key nor the current tenant key \u2014 surfaced in the closing summary so an\n // operator does not have to grep console.warn output for a batch of thousands.\n const unrecoverableRows: Array<{ entityId: string; field: string; rowId: unknown }> = []\n // A dry run must not provision key material. `encryptEntityPayload` creates and\n // persists a tenant DEK in KMS/Vault the first time it runs for a tenant, so a\n // read-only preview would silently mutate KMS state (#5950). Probe read-only\n // once per tenant instead, and report what a real run would rewrite.\n //\n // The tenant id IS the key id here: this command skips every system-scoped\n // entity above, and its service is built without `defaultEncryptionMaps`, so\n // no map it sees can resolve to a `system:<entityId>` key.\n const dekAvailability = new Map<string, boolean>()\n const hasExistingDek = async (tenantId: string): Promise<boolean> => {\n const cached = dekAvailability.get(tenantId)\n if (cached !== undefined) return cached\n const available = Boolean(await encryptionService.getDek(tenantId))\n dekAvailability.set(tenantId, available)\n if (!available) {\n console.warn(\n `[dry-run] Tenant ${tenantId} has no data-encryption key yet. Reporting the rows a real run would encrypt; no key material was provisioned.`,\n )\n }\n return available\n }\n const processScope = async (\n entityId: string,\n meta: any,\n fields: Array<{ field: string; hashField?: string | null }>,\n scope: { tenantId: string; organizationId: string | null },\n ): Promise<number> => {\n const pk = Array.isArray(meta?.primaryKeys) && meta.primaryKeys.length ? meta.primaryKeys[0] : 'id'\n const columns = new Set<string>()\n columns.add(pk)\n for (const rule of fields) {\n const resolved = resolveProperty(meta, rule.field)\n if (resolved?.columnName) columns.add(resolved.columnName)\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n if (resolvedHash?.columnName) columns.add(resolvedHash.columnName)\n }\n }\n const columnList = Array.from(columns)\n if (!columnList.length) return 0\n const tableName = meta?.tableName\n if (!tableName) return 0\n const schema = meta?.schema\n const qualifiedTable = schema ? `\"${schema}\".\"${tableName}\"` : `\"${tableName}\"`\n const selectSql = `select ${columnList.map((c) => `\"${c}\"`).join(', ')} from ${qualifiedTable} where tenant_id = ? and organization_id is not distinct from ?`\n const rows = await conn.execute(selectSql, [scope.tenantId, scope.organizationId])\n const list = Array.isArray(rows) ? rows : []\n if (!list.length) return 0\n const dekAvailable = dryRun ? await hasExistingDek(scope.tenantId) : true\n // The scope pins one tenant, so the current DEK is the same for every row here.\n const currentDek = rotate && oldKms ? await encryptionService.getDek(scope.tenantId) : null\n let updated = 0\n for (const row of list) {\n const payload: Record<string, unknown> = {}\n for (const rule of fields) {\n const resolved = resolveProperty(meta, rule.field)\n const col = resolved?.columnName\n if (!col) continue\n const rawValue = row[col]\n // Rotate mode only touches values that look like an envelope (candidates to\n // re-key); encrypt mode only touches values that do not (plaintext to seal).\n // A shape-valid envelope encountered in encrypt mode is either already\n // correctly encrypted or sealed under a foreign DEK \u2014 either way, handing it\n // to encryptEntityPayload would either no-op or throw WRONG_KEY and abort the\n // whole run (#5951); mirror the filter the update path already applies below.\n if (isEncryptedPayloadShape(rawValue) ? !rotate : rotate) {\n continue\n }\n payload[rule.field] = rawValue\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n const hashCol = resolvedHash?.columnName\n if (hashCol) payload[rule.hashField] = row[hashCol]\n }\n }\n if (rotate && !Object.keys(payload).length) {\n continue\n }\n if (rotate && oldKms) {\n let oldDek = oldDekCache.get(scope.tenantId) ?? null\n if (!oldDekCache.has(scope.tenantId)) {\n oldDek = await oldKms.getTenantDek(scope.tenantId)\n oldDekCache.set(scope.tenantId, oldDek)\n }\n for (const rule of fields) {\n const value = payload[rule.field]\n if (typeof value !== 'string' || !isEncryptedPayloadShape(value)) continue\n const decrypted = decryptWithOldKey(value, oldDek)\n if (decrypted !== null) {\n payload[rule.field] = parseDecryptedFieldValue(decrypted)\n continue\n }\n // The old key did not open it. If the current key does, the row was already\n // rotated (a resumed run) \u2014 leave it in the payload, where the service's own\n // already-encrypted check skips it. Otherwise no key we hold can open it, and\n // handing it to the encrypt path would abort the whole batch (#5951). Drop the\n // field so this row is reported and skipped instead.\n if (currentDek && decryptWithAesGcm(value, currentDek.key) !== null) continue\n delete payload[rule.field]\n if (rule.hashField) delete payload[rule.hashField]\n unrecoverableRows.push({ entityId, field: rule.field, rowId: row[pk] })\n console.warn(\n `Skipping ${entityId}.${rule.field} for row ${row[pk]}: its ciphertext opens under neither --old-key nor the current tenant key. Re-run with the key that sealed it.`,\n )\n }\n }\n if (!dekAvailable) {\n // Nothing was encrypted because no key exists and this run refuses to\n // create one. Count the rows a real run would rewrite by applying the\n // same plaintext/ciphertext filters the update path uses below.\n const wouldChange = fields.some((rule) => {\n const col = resolveProperty(meta, rule.field)?.columnName\n if (!col) return false\n const value = row[col]\n if (value === null || value === undefined) return false\n return rotate ? isEncryptedPayloadShape(value) : !isEncryptedPayloadShape(value)\n })\n if (wouldChange) updated += 1\n continue\n }\n const encrypted = await encryptionService.encryptEntityPayload(\n entityId,\n payload,\n scope.tenantId,\n scope.organizationId,\n { createMissingDek: !dryRun },\n )\n const updates: Record<string, unknown> = {}\n for (const rule of fields) {\n const resolved = resolveProperty(meta, rule.field)\n const col = resolved?.columnName\n if (!col) continue\n const nextValue = (encrypted as any)[rule.field]\n if (nextValue !== undefined && nextValue !== row[col]) {\n if (!rotate && isEncryptedPayloadShape(row[col])) continue\n updates[col] = formatValueForColumn(resolved?.prop, nextValue)\n }\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n const hashCol = resolvedHash?.columnName\n const hashValue = (encrypted as any)[rule.hashField]\n if (hashCol && hashValue !== undefined && hashValue !== row[hashCol]) {\n updates[hashCol] = formatValueForColumn(resolvedHash?.prop, hashValue)\n }\n }\n }\n if (!Object.keys(updates).length) continue\n if (!dryRun) {\n const setSql = Object.keys(updates).map((col) => `\"${col}\" = ?`).join(', ')\n await conn.execute(\n `update ${qualifiedTable} set ${setSql} where \"${pk}\" = ?`,\n [...Object.values(updates), row[pk]],\n )\n }\n updated += 1\n }\n return updated\n }\n\n let total = 0\n for (const map of maps) {\n const mapMeta = resolveMapMeta(map, metaByEntityId, console.warn)\n if (!mapMeta) continue\n const { entityId, meta, fields, tenantId } = mapMeta\n const scopes = await resolveScopes(tenantId, map.organizationId ? String(map.organizationId) : null)\n for (const scope of scopes) {\n const updated = await processScope(entityId, meta, fields, scope)\n if (updated > 0) {\n console.log(\n `${dryRun ? '[dry-run] ' : ''}Encrypted ${updated} record(s) for ${entityId} org=${scope.organizationId ?? 'null'}`\n )\n }\n total += updated\n }\n }\n\n if (total > 0) {\n console.log(`Encrypted ${total} record(s) across mapped entities.`)\n } else {\n console.log('All mapped entity fields already encrypted for the selected scope.')\n }\n if (unrecoverableRows.length) {\n const maxListed = 20\n console.log(\n `\\n\u26A0\uFE0F ${unrecoverableRows.length} field(s) could not be rotated \u2014 their ciphertext opens under neither `\n + '--old-key nor the current tenant key, so they were left untouched. See '\n + '\"Key management\" in apps/docs/docs/architecture/data-encryption.mdx for how to resolve these manually.',\n )\n for (const row of unrecoverableRows.slice(0, maxListed)) {\n console.log(` - ${row.entityId}.${row.field} row=${String(row.rowId)}`)\n }\n if (unrecoverableRows.length > maxListed) {\n console.log(` ... and ${unrecoverableRows.length - maxListed} more`)\n }\n }\n },\n}\n\nconst decryptDatabase: ModuleCli = {\n command: 'decrypt-database',\n async run(rest) {\n const args = parseArgs(rest)\n const tenantIdArg = (args.tenant as string) || (args.tenantId as string) || null\n const organizationIdArg = (args.org as string) || (args.organization as string) || (args.organizationId as string) || null\n const entityIdArg = (args.entity as string) || null\n const checkMode = Boolean(args.check)\n const dryRun = Boolean(args['dry-run'] || args.dry) || checkMode\n const deactivateMaps = Boolean(args['deactivate-maps'])\n const confirm = (args.confirm as string) || null\n const batchSize = Math.max(1, parseInt(String(args['batch-size'] || args.batchSize || '500'), 10) || 500)\n const sleepMs = Math.max(0, parseInt(String(args['sleep-ms'] || args.sleepMs || '0'), 10) || 0)\n const debug = Boolean(args.debug)\n\n if (!tenantIdArg) {\n console.error('--tenant <uuid> is required.')\n return\n }\n\n if (!checkMode) {\n if (!confirm) {\n console.error('--confirm <tenantUuid> is required (safety gate). Pass the exact tenant UUID to confirm the operation.')\n return\n }\n if (confirm !== tenantIdArg) {\n console.error(`--confirm value \"${confirm}\" does not match --tenant \"${tenantIdArg}\". Aborting.`)\n return\n }\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const conn: any = em?.getConnection?.()\n if (!conn || typeof conn.execute !== 'function') {\n console.error('Unable to access raw database connection; aborting.')\n return\n }\n\n if (!checkMode && !isTenantDataEncryptionEnabled()) {\n console.error('TENANT_DATA_ENCRYPTION is disabled; aborting. Data may already be decrypted.')\n return\n }\n\n const metaByEntityId = buildEntityMetaRegistry(em)\n\n const resolveDecryptScopes = async (\n tenantId: string,\n organizationId: string | null,\n ): Promise<Array<{ tenantId: string; organizationId: string | null }>> => {\n if (organizationId) return [{ tenantId, organizationId }]\n const rows = await conn.execute(\n `SELECT DISTINCT organization_id FROM encryption_maps WHERE tenant_id = ? AND deleted_at IS NULL`,\n [tenantId],\n )\n const orgIds = new Set<string | null>()\n for (const row of Array.isArray(rows) ? rows : []) {\n orgIds.add(row.organization_id ?? null)\n }\n orgIds.add(null)\n return Array.from(orgIds).map((orgId) => ({ tenantId, organizationId: orgId }))\n }\n\n const mapWhere: any = { tenantId: tenantIdArg, deletedAt: null, isActive: true }\n if (organizationIdArg) mapWhere.organizationId = organizationIdArg\n if (entityIdArg) mapWhere.entityId = entityIdArg\n const allMaps = await em.find(EncryptionMap, mapWhere)\n const systemScopedEntityIds = await getSystemScopedEntityIds()\n const maps = allMaps.filter((map: EncryptionMap) => {\n if (!systemScopedEntityIds.has(String(map.entityId))) return true\n console.warn(\n `Skipping ${map.entityId}: system-scoped entity. Its ciphertext is sealed under a system key that the tenant DEK cannot open, so this command leaves it untouched.`,\n )\n return false\n })\n\n if (!maps.length) {\n console.log('No active encryption maps found for the selected scope.')\n return\n }\n\n const kms = createKmsService()\n const dekCache = new Map<string, TenantDek | null>()\n const getDek = async (tenantId: string): Promise<TenantDek | null> => {\n if (dekCache.has(tenantId)) return dekCache.get(tenantId) ?? null\n const dek = await kms.getTenantDek(tenantId)\n dekCache.set(tenantId, dek)\n return dek\n }\n\n if (checkMode) {\n const envValue = process.env.TENANT_DATA_ENCRYPTION ?? '(not set)'\n console.log(`TENANT_DATA_ENCRYPTION = ${envValue}`)\n console.log(`Active EncryptionMap records for scope: ${maps.length}`)\n let encryptedCandidatesSampled = 0\n let malformedPayloadCountSampled = 0\n for (const map of maps) {\n const mapMeta = resolveMapMeta(map, metaByEntityId)\n if (!mapMeta) continue\n const { entityId, meta, fields, tenantId } = mapMeta\n const dek = await getDek(tenantId).catch(() => null)\n if (!dek) continue\n const scopes = await resolveDecryptScopes(tenantId, map.organizationId ? String(map.organizationId) : null)\n const pk = Array.isArray(meta?.primaryKeys) && meta.primaryKeys.length ? meta.primaryKeys[0] : 'id'\n const tableName = meta?.tableName\n if (!tableName) continue\n const schema = meta?.schema\n const qualifiedTable = schema ? `\"${schema}\".\"${tableName}\"` : `\"${tableName}\"`\n const fieldCols = fields.flatMap((f: any) => {\n const r = resolveProperty(meta, f.field)\n return r.columnName ? [r.columnName] : []\n })\n const colList = Array.from(new Set([pk, ...fieldCols]))\n for (const scope of scopes) {\n const sampleRows = await conn.execute(\n `SELECT ${colList.map((c: string) => `\"${c}\"`).join(', ')} FROM ${qualifiedTable} WHERE tenant_id = ? AND organization_id IS NOT DISTINCT FROM ? LIMIT 100`,\n [scope.tenantId, scope.organizationId],\n ).catch(() => [])\n for (const row of Array.isArray(sampleRows) ? sampleRows : []) {\n let rowHasEncrypted = false\n for (const fieldRule of fields) {\n const resolved = resolveProperty(meta, fieldRule.field)\n const col = resolved?.columnName\n if (!col) continue\n const rawValue = row[col]\n if (rawValue === null || rawValue === undefined) continue\n try {\n decryptWithAesGcmStrict(String(rawValue), dek.key)\n rowHasEncrypted = true\n } catch (e: any) {\n if (e instanceof TenantDataEncryptionError && e.code === TenantDataEncryptionErrorCode.MALFORMED_PAYLOAD) {\n malformedPayloadCountSampled++\n }\n }\n }\n if (rowHasEncrypted) encryptedCandidatesSampled++\n }\n }\n }\n console.log(`estimated encrypted candidates (sampled): ${encryptedCandidatesSampled}`)\n if (malformedPayloadCountSampled > 0) {\n console.warn(`\u26A0 malformed payloads (sampled): ${malformedPayloadCountSampled} \u2014 may indicate corruption`)\n } else {\n console.log(`malformed payloads (sampled): ${malformedPayloadCountSampled}`)\n }\n console.log('not a proof of absence \u2014 run full command + rerun --check to confirm')\n return\n }\n\n let totalRowsFetched = 0\n let totalRowsUpdated = 0\n let totalHashFieldsCleared = 0\n const totalHashFieldsSkipped = new Set<string>()\n let totalMalformedPayloadCount = 0\n const malformedByLocation = new Map<string, number>()\n let totalEntitiesProcessed = 0\n\n for (const map of maps) {\n const mapMeta = resolveMapMeta(map, metaByEntityId, console.warn)\n if (!mapMeta) continue\n const { entityId, meta, fields, tenantId } = mapMeta\n const dek = await getDek(tenantId)\n if (!dek) {\n console.warn(`No DEK available for tenant ${tenantId}; skipping ${entityId}.`)\n continue\n }\n const pk = Array.isArray(meta?.primaryKeys) && meta.primaryKeys.length ? meta.primaryKeys[0] : 'id'\n const tableName = meta?.tableName\n if (!tableName) {\n console.warn(`Skipping ${entityId}: table name not found.`)\n continue\n }\n const schema = meta?.schema\n const qualifiedTable = schema ? `\"${schema}\".\"${tableName}\"` : `\"${tableName}\"`\n const fieldCols = fields.flatMap((f: any) => {\n const r = resolveProperty(meta, f.field)\n return r.columnName ? [r.columnName] : []\n })\n const colList = Array.from(new Set([pk, ...fieldCols]))\n totalEntitiesProcessed++\n\n const scopes = await resolveDecryptScopes(tenantId, map.organizationId ? String(map.organizationId) : null)\n\n for (const scope of scopes) {\n let lastId: string | null = null\n let scopeMalformedCount = 0\n\n while (true) {\n let selectSql = `SELECT ${colList.map((c: string) => `\"${c}\"`).join(', ')} FROM ${qualifiedTable} WHERE tenant_id = ? AND organization_id IS NOT DISTINCT FROM ?`\n const selectParams: unknown[] = [scope.tenantId, scope.organizationId]\n if (lastId !== null) {\n selectSql += ` AND \"${pk}\" > ?`\n selectParams.push(lastId)\n }\n selectSql += ` ORDER BY \"${pk}\" LIMIT ?`\n selectParams.push(batchSize)\n\n const batchRows = await conn.execute(selectSql, selectParams)\n const batch = Array.isArray(batchRows) ? batchRows : []\n if (!batch.length) break\n\n lastId = String(batch[batch.length - 1]![pk])\n totalRowsFetched += batch.length\n\n const batchStart = Date.now()\n await conn.execute('BEGIN')\n let batchCommitted = false\n try {\n for (const row of batch) {\n const updates: Record<string, unknown> = {}\n let rowDecrypted = false\n\n for (const fieldRule of fields) {\n const resolved = resolveProperty(meta, fieldRule.field)\n const col = resolved?.columnName\n if (!col) continue\n const rawValue = row[col]\n if (rawValue === null || rawValue === undefined) continue\n try {\n const decrypted = decryptWithAesGcmStrict(String(rawValue), dek.key)\n let valueToWrite: string\n try {\n const parsed = JSON.parse(decrypted)\n valueToWrite = typeof parsed === 'string' ? parsed : decrypted\n } catch {\n valueToWrite = decrypted\n }\n updates[col] = valueToWrite\n rowDecrypted = true\n } catch (e: any) {\n if (e instanceof TenantDataEncryptionError) {\n if (e.code === TenantDataEncryptionErrorCode.AUTH_FAILED) {\n // Value is plaintext \u2014 skip silently\n } else if (e.code === TenantDataEncryptionErrorCode.MALFORMED_PAYLOAD) {\n scopeMalformedCount++\n const locationKey = `${tableName}:${col}`\n malformedByLocation.set(locationKey, (malformedByLocation.get(locationKey) ?? 0) + 1)\n console.warn(`\u26A0 MALFORMED_PAYLOAD for ${entityId} field \"${col}\" row ${row[pk]}; skipping field.`)\n } else {\n throw e\n }\n } else {\n throw e\n }\n }\n }\n\n if (rowDecrypted) {\n for (const fieldRule of fields) {\n if (!fieldRule.hashField) continue\n const resolvedHash = resolveProperty(meta, fieldRule.hashField)\n const hashCol = resolvedHash?.columnName\n if (!hashCol) {\n const skippedKey = `${tableName}:${fieldRule.hashField}`\n if (!totalHashFieldsSkipped.has(skippedKey)) {\n console.warn(`\u26A0 Hash column \"${fieldRule.hashField}\" not found in metadata for ${entityId}; skipping.`)\n totalHashFieldsSkipped.add(skippedKey)\n }\n continue\n }\n updates[hashCol] = null\n totalHashFieldsCleared++\n }\n }\n\n if (Object.keys(updates).length > 0) {\n if (!dryRun) {\n const setSql = Object.keys(updates).map((col) => `\"${col}\" = ?`).join(', ')\n await conn.execute(\n `UPDATE ${qualifiedTable} SET ${setSql} WHERE \"${pk}\" = ?`,\n [...Object.values(updates), row[pk]],\n )\n }\n totalRowsUpdated++\n }\n }\n await conn.execute('COMMIT')\n batchCommitted = true\n } catch (fatalErr: any) {\n if (!batchCommitted) {\n try { await conn.execute('ROLLBACK') } catch {}\n }\n console.error(`Fatal error during batch processing for ${entityId}: ${(fatalErr as Error)?.message || String(fatalErr)}`)\n throw fatalErr\n }\n\n const batchDurationMs = Date.now() - batchStart\n if (debug) {\n console.log(\n `[debug] Batch ${entityId} org=${scope.organizationId ?? 'null'}: ${batch.length} rows in ${batchDurationMs}ms, scopeMalformed=${scopeMalformedCount}`,\n )\n if (batchDurationMs > 30_000) {\n console.warn('\u26A0 Batch took >30s; consider reducing --batch-size.')\n }\n }\n if (sleepMs > 0) {\n await new Promise<void>((r) => setTimeout(r, sleepMs))\n }\n }\n\n totalMalformedPayloadCount += scopeMalformedCount\n }\n }\n\n if (deactivateMaps && !dryRun) {\n let deactivateSql = `UPDATE encryption_maps SET is_active = false, deleted_at = now() WHERE tenant_id = ? AND deleted_at IS NULL`\n const deactivateParams: unknown[] = [tenantIdArg]\n if (organizationIdArg) {\n deactivateSql += ` AND (organization_id = ? OR organization_id IS NULL)`\n deactivateParams.push(organizationIdArg)\n }\n if (entityIdArg) {\n deactivateSql += ` AND entity_id = ?`\n deactivateParams.push(entityIdArg)\n }\n await conn.execute(deactivateSql, deactivateParams)\n console.warn('\u26A0 Restart all application replicas \u2014 in-process map caches may still be active.')\n if (isTenantDataEncryptionEnabled()) {\n console.warn('\u26A0 Env TENANT_DATA_ENCRYPTION is still true \u2014 new writes will be re-encrypted until env is updated and replicas restarted.')\n }\n }\n\n if (deactivateMaps && dryRun) {\n console.log(`[dry-run] Would deactivate ${maps.length} EncryptionMap record(s).`)\n }\n\n const prefix = dryRun ? '[dry-run] ' : ''\n console.log(`\\n${prefix}Decryption summary:`)\n console.log(` Rows fetched: ${totalRowsFetched}`)\n console.log(` Rows updated: ${totalRowsUpdated}`)\n console.log(` Entities processed: ${totalEntitiesProcessed}`)\n console.log(` Hash fields cleared: ${totalHashFieldsCleared}`)\n if (totalHashFieldsSkipped.size > 0) {\n console.log(` Hash fields skipped (missing columns): ${Array.from(totalHashFieldsSkipped).join(', ')}`)\n }\n if (totalMalformedPayloadCount > 0) {\n console.warn(\n ` \u26A0 ${totalMalformedPayloadCount} field value(s) returned MALFORMED_PAYLOAD and were skipped; these may be corrupted ciphertexts. Investigate before assuming decryption is complete.`,\n )\n if (debug && malformedByLocation.size > 0) {\n const top = Array.from(malformedByLocation.entries())\n .sort((a, b) => b[1] - a[1])\n .slice(0, 10)\n console.log(' Top malformed locations:')\n for (const [loc, count] of top) {\n console.log(` ${loc}: ${count}`)\n }\n }\n }\n\n if (!dryRun) {\n console.log(`\\n\u2705 Decryption complete. Required next steps:`)\n console.log(` 1. Set TENANT_DATA_ENCRYPTION=false in your environment / secrets`)\n console.log(` 2. Restart all application replicas`)\n console.log(` 3. Run: mercato query_index reindex --tenant ${tenantIdArg} \u2190 run after env flip + restart; search/filter degraded until this completes`)\n console.log(` 4. Run: mercato entities decrypt-database --tenant ${tenantIdArg} --check to confirm no encrypted values remain`)\n console.log(` NOTE: if the run was long and concurrent inserts occurred, run again before step 4 \u2014 it is idempotent.`)\n }\n },\n}\n\n/**\n * Encrypt rows that a **system-scoped** encryption map covers but that were written\n * before the map existed.\n *\n * `rotate-encryption-key` cannot reach them: it walks the `encryption_maps` table and\n * skips every row without a `tenant_id`, while system-scoped maps are declared in module\n * code (`defaultEncryptionMaps` with `keyScope: 'system'`) and cover pre-tenant records\n * that have no tenant at all \u2014 onboarding requests being the first such entity. Without a\n * backfill those historical rows stay plaintext forever, since only the write path\n * encrypts.\n *\n * The command is forward-only (plaintext \u2192 ciphertext), never accepts an old key and never\n * decrypts, so it cannot re-encrypt data under the wrong key. It is idempotent: values that\n * already decrypt under the current system DEK are left untouched by\n * `encryptEntityPayload`, so a partial run can simply be repeated.\n */\nconst backfillSystemEncryption: ModuleCli = {\n command: 'backfill-system-encryption',\n async run(rest) {\n const args = parseArgs(rest)\n const entityIdArg = (args.entity as string) || null\n const dryRun = Boolean(args['dry-run'] || args.dry)\n const batchSize = Math.max(1, parseInt(String(args['batch-size'] || args.batchSize || '500'), 10) || 500)\n const debug = Boolean(args.debug)\n\n if (!isTenantDataEncryptionEnabled()) {\n console.error('TENANT_DATA_ENCRYPTION is disabled; aborting. Enable encryption before backfilling.')\n return\n }\n\n const systemMaps = getDefaultEncryptionMaps(await resolveEncryptionMapModules()).filter((map) => map.keyScope === 'system')\n if (!systemMaps.length) {\n console.log('No system-scoped encryption maps are declared by the installed modules. Nothing to backfill.')\n return\n }\n const selectedMaps = entityIdArg ? systemMaps.filter((map) => map.entityId === entityIdArg) : systemMaps\n if (!selectedMaps.length) {\n console.error(`No system-scoped encryption map declared for entity \"${entityIdArg}\".`)\n console.error(`Known system-scoped entities: ${systemMaps.map((map) => map.entityId).join(', ')}`)\n return\n }\n\n const { resolve } = await createRequestContainer()\n const em = resolve('em') as any\n const conn: any = em?.getConnection?.()\n if (!conn || typeof conn.execute !== 'function') {\n console.error('Unable to access raw database connection; aborting.')\n return\n }\n\n const encryptionService = new TenantDataEncryptionService(em as any, {\n kms: createKmsService(),\n defaultEncryptionMaps: systemMaps,\n })\n if (!encryptionService.isEnabled()) {\n console.error('Encryption service is not enabled (KMS unhealthy). Aborting without touching any row.')\n return\n }\n\n const metaByEntityId = buildEntityMetaRegistry(em)\n const prefix = dryRun ? '[dry-run] ' : ''\n let totalRowsScanned = 0\n let totalRowsUpdated = 0\n let totalRowsUnchanged = 0\n\n for (const map of selectedMaps) {\n const entityId = map.entityId\n const meta = metaByEntityId.get(entityId)\n if (!meta) {\n console.warn(`Skipping ${entityId}: entity metadata not found (is the owning module enabled?).`)\n continue\n }\n const tableName = meta?.tableName\n if (!tableName) {\n console.warn(`Skipping ${entityId}: entity has no table name.`)\n continue\n }\n const primaryKey = Array.isArray(meta?.primaryKeys) && meta.primaryKeys.length ? meta.primaryKeys[0] : 'id'\n const { columnName: primaryKeyColumn } = resolveProperty(meta, primaryKey)\n const pkColumn = primaryKeyColumn ?? primaryKey\n const qualifiedTable = meta?.schema ? `\"${meta.schema}\".\"${tableName}\"` : `\"${tableName}\"`\n\n const columns = new Set<string>([pkColumn])\n for (const rule of map.fields) {\n const resolved = resolveProperty(meta, rule.field)\n if (resolved.columnName) columns.add(resolved.columnName)\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n if (resolvedHash.columnName) columns.add(resolvedHash.columnName)\n }\n }\n const columnList = Array.from(columns)\n const selectList = columnList.map((column) => `\"${column}\"`).join(', ')\n\n // Same dry-run guarantee as rotate-encryption-key: previewing must not make\n // the KMS provision this entity's system DEK as a side effect (#5950).\n const systemDekAvailable = dryRun\n ? Boolean(await encryptionService.getDek(resolveEncryptionKeyId(entityId, 'system', null)))\n : true\n if (!systemDekAvailable) {\n console.warn(\n `[dry-run] ${entityId} has no system data-encryption key yet. Reporting the rows a real run would encrypt; no key material was provisioned.`,\n )\n }\n\n let cursor: unknown = null\n let entityRowsScanned = 0\n let entityRowsUpdated = 0\n let entityRowsUnchanged = 0\n\n for (;;) {\n const selectSql = cursor === null\n ? `select ${selectList} from ${qualifiedTable} order by \"${pkColumn}\" asc limit ?`\n : `select ${selectList} from ${qualifiedTable} where \"${pkColumn}\" > ? order by \"${pkColumn}\" asc limit ?`\n const params = cursor === null ? [batchSize] : [cursor, batchSize]\n const rows = await conn.execute(selectSql, params)\n const list = Array.isArray(rows) ? rows : []\n if (!list.length) break\n\n for (const row of list) {\n entityRowsScanned += 1\n cursor = row[pkColumn]\n const payload: Record<string, unknown> = {}\n let hasPlaintext = false\n for (const rule of map.fields) {\n const resolved = resolveProperty(meta, rule.field)\n if (!resolved.columnName) continue\n const rawValue = row[resolved.columnName]\n // Only plaintext belongs in the payload. An already-encrypted column produced no\n // update anyway (the service skips what decrypts under the current key), but if the\n // system key ever changed, passing its ciphertext through would abort the whole\n // backfill rather than let the row's genuinely-plaintext fields through (#5951).\n if (rawValue !== null && rawValue !== undefined && isEncryptedPayloadShape(rawValue)) continue\n payload[rule.field] = rawValue\n if (rawValue !== null && rawValue !== undefined) hasPlaintext = true\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n if (resolvedHash.columnName) payload[rule.hashField] = row[resolvedHash.columnName]\n }\n }\n if (!hasPlaintext) continue\n if (!systemDekAvailable) {\n // `hasPlaintext` already means a real run would rewrite this row.\n entityRowsUpdated += 1\n continue\n }\n\n const encrypted = await encryptionService.encryptEntityPayload(\n entityId,\n payload,\n null,\n null,\n { createMissingDek: !dryRun },\n )\n const updates: Record<string, unknown> = {}\n for (const rule of map.fields) {\n const resolved = resolveProperty(meta, rule.field)\n if (resolved.columnName) {\n const nextValue = encrypted[rule.field]\n if (nextValue !== undefined && nextValue !== row[resolved.columnName]) {\n updates[resolved.columnName] = formatValueForColumn(resolved.prop, nextValue)\n }\n }\n if (rule.hashField) {\n const resolvedHash = resolveProperty(meta, rule.hashField)\n const nextHash = encrypted[rule.hashField]\n if (resolvedHash.columnName && nextHash !== undefined && nextHash !== row[resolvedHash.columnName]) {\n updates[resolvedHash.columnName] = formatValueForColumn(resolvedHash.prop, nextHash)\n }\n }\n }\n if (!Object.keys(updates).length) {\n entityRowsUnchanged += 1\n if (debug) console.warn(`[backfill-system-encryption] ${entityId} ${String(row[pkColumn])}: plaintext left unchanged`)\n continue\n }\n if (!dryRun) {\n const setSql = Object.keys(updates).map((column) => `\"${column}\" = ?`).join(', ')\n await conn.execute(\n `update ${qualifiedTable} set ${setSql} where \"${pkColumn}\" = ?`,\n [...Object.values(updates), row[pkColumn]],\n )\n }\n entityRowsUpdated += 1\n }\n\n if (list.length < batchSize) break\n }\n\n totalRowsScanned += entityRowsScanned\n totalRowsUpdated += entityRowsUpdated\n totalRowsUnchanged += entityRowsUnchanged\n console.log(`${prefix}${entityId}: scanned ${entityRowsScanned}, encrypted ${entityRowsUpdated}`)\n }\n\n console.log(`\\n${prefix}Backfill summary:`)\n console.log(` Rows scanned: ${totalRowsScanned}`)\n console.log(` Rows encrypted: ${totalRowsUpdated}`)\n if (totalRowsUnchanged > 0) {\n console.warn(\n ` \u26A0 ${totalRowsUnchanged} row(s) held plaintext that could not be encrypted \u2014 the system DEK was unavailable. Verify the KMS/fallback key, then re-run (use --debug to list the rows).`,\n )\n }\n if (!dryRun && totalRowsUpdated > 0) {\n console.log('\\n\u2705 Backfill complete. Re-run with --dry-run to confirm no plaintext rows remain.')\n }\n },\n}\n\n// Keep default export stable (install first for help listing)\nexport default [seedDefs, reinstallDefs, addField, seedEncryptionMaps, rotateEncryptionKey, decryptDatabase, backfillSystemEncryption]\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -5,6 +5,18 @@ import {
|
|
|
5
5
|
} from "@open-mercato/shared/modules/entities/validation";
|
|
6
6
|
import { CustomFieldValue } from "../data/entities.js";
|
|
7
7
|
import { loadScopedCustomFieldDefs } from "./scoped-field-defs.js";
|
|
8
|
+
import { encryptCustomEntityDocument } from "@open-mercato/shared/lib/encryption/customEntityDoc";
|
|
9
|
+
import { isTenantDataEncryptionEnabled } from "@open-mercato/shared/lib/encryption/toggles";
|
|
10
|
+
async function encryptCustomEntityRecordDocument(em, opts) {
|
|
11
|
+
if (!isTenantDataEncryptionEnabled() || Object.keys(opts.doc).every((key) => key === "id")) return opts.doc;
|
|
12
|
+
const definitions = await loadScopedCustomFieldDefs(em, opts);
|
|
13
|
+
const fields = Array.from(definitions.values(), (definition) => ({
|
|
14
|
+
key: definition.key,
|
|
15
|
+
kind: definition.kind,
|
|
16
|
+
encrypted: Boolean(definition.configJson?.encrypted)
|
|
17
|
+
}));
|
|
18
|
+
return encryptCustomEntityDocument(opts.doc, fields, opts.tenantId, resolveTenantEncryptionService(em, opts.encryptionService));
|
|
19
|
+
}
|
|
8
20
|
function columnFromKind(kind) {
|
|
9
21
|
switch (kind) {
|
|
10
22
|
case "text":
|
|
@@ -176,6 +188,7 @@ async function setRecordCustomFields(em, opts) {
|
|
|
176
188
|
}
|
|
177
189
|
}
|
|
178
190
|
export {
|
|
191
|
+
encryptCustomEntityRecordDocument,
|
|
179
192
|
setRecordCustomFields
|
|
180
193
|
};
|
|
181
194
|
//# sourceMappingURL=helpers.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/entities/lib/helpers.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { decryptCustomFieldValue, encryptCustomFieldValue, resolveTenantEncryptionService } from '@open-mercato/shared/lib/encryption/customFieldValues'\nimport {\n MAX_CUSTOM_FIELD_KEYS_PER_RECORD,\n TOO_MANY_CUSTOM_FIELDS_ERROR,\n} from '@open-mercato/shared/modules/entities/validation'\nimport { CustomFieldDef, CustomFieldValue } from '../data/entities'\nimport { loadScopedCustomFieldDefs } from './scoped-field-defs'\n\ntype Primitive = string | number | boolean | null | undefined\ntype PrimitiveOrArray = Primitive | Primitive[]\n\nexport type SetRecordCustomFieldsOptions = {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n values: Record<string, PrimitiveOrArray>\n // When true (default), try to use field definitions to decide storage column\n preferDefs?: boolean\n // Optional: notify external systems (e.g., indexing) when values changed\n onChanged?: (payload: { entityId: string; recordId: string; organizationId: string | null; tenantId: string | null }) => Promise<void> | void\n // Optional: re-use an existing tenant encryption service instance\n encryptionService?: TenantDataEncryptionService | null\n // When true, additionally scope the reconciling delete/lookup by organizationId,\n // restoring the pre-#5970 organization pin. Default false relies on `recordId`\n // being unique across organizations for a given entityId \u2014 true for every\n // ORM-backed entity, where recordId is the record's own primary key. That\n // invariant does not hold for `custom_entities_storage` records written under\n // `ENTITIES_BACKCOMPAT_EAV_FOR_CUSTOM`: the table upserts on\n // `(entity_type, entity_id, organization_id)`, so the same id can be a live\n // record in two organizations of one tenant, and an organization-agnostic\n // delete could remove another organization's values (#6034 review). Callers\n // writing those records must pass pinOrganizationId: true.\n pinOrganizationId?: boolean\n}\n\nfunction columnFromKind(kind: string): keyof CustomFieldValue {\n switch (kind) {\n case 'text':\n case 'select':\n case 'currency':\n case 'dictionary':\n case 'phone':\n return 'valueText'\n case 'multiline':\n return 'valueMultiline'\n case 'integer':\n return 'valueInt'\n case 'float':\n return 'valueFloat'\n case 'boolean':\n return 'valueBool'\n default:\n return 'valueText'\n }\n}\n\nfunction columnFromJsValue(v: Primitive): keyof CustomFieldValue {\n if (v === null || v === undefined) return 'valueText'\n if (typeof v === 'boolean') return 'valueBool'\n if (typeof v === 'number') return Number.isInteger(v) ? 'valueInt' : 'valueFloat'\n return 'valueText'\n}\n\n// Clears all value columns to avoid leftovers on update\nfunction clearValueColumns(cf: CustomFieldValue) {\n cf.valueText = null\n cf.valueMultiline = null\n cf.valueInt = null\n cf.valueFloat = null\n cf.valueBool = null\n}\n\nfunction valueFromRow(row: CustomFieldValue): Primitive {\n if (row.valueMultiline != null) return row.valueMultiline\n if (row.valueText != null) return row.valueText\n if (row.valueInt != null) return row.valueInt\n if (row.valueFloat != null) return row.valueFloat\n if (row.valueBool != null) return row.valueBool\n return null\n}\n\n// Keeps the value the caller's own scope already holds when the echoed array contains it,\n// so a save that never touched the field does not swap in another organization's value.\nfunction pickSingleValue(candidates: Primitive[], currentValue: unknown): Primitive {\n if (currentValue != null) {\n const kept = candidates.find((candidate) => candidate != null && String(candidate) === String(currentValue))\n if (kept !== undefined) return kept\n }\n const present = candidates.filter((candidate) => candidate != null)\n return present.length ? present[present.length - 1] : null\n}\n\nexport async function setRecordCustomFields(\n em: EntityManager,\n opts: SetRecordCustomFieldsOptions,\n): Promise<void> {\n const { entityId, recordId, values } = opts\n const organizationId = opts.organizationId ?? null\n const tenantId = opts.tenantId ?? null\n const preferDefs = opts.preferDefs !== false\n\n let defsByKey: Map<string, CustomFieldDef> | undefined\n if (preferDefs) {\n defsByKey = await loadScopedCustomFieldDefs(em, { entityId, organizationId, tenantId })\n }\n\n const toPersist: CustomFieldValue[] = []\n let encryptionService: TenantDataEncryptionService | null | undefined\n const encryptionCache = new Map<string | null, string | null>()\n const getEncryptionService = () => {\n if (encryptionService !== undefined) return encryptionService\n encryptionService = resolveTenantEncryptionService(em as any, opts.encryptionService)\n return encryptionService\n }\n // A write owns every value row a reader in ITS OWN tenant scope could return for this\n // record: same logical key (entityId, recordId, fieldKey), any organization, and either\n // the caller's tenant or the instance-global NULL tenant \u2014 exactly what the Query Engine\n // matches. Organization is left out because the Query Engine's value join does not filter\n // it (engine.ts, the cf join and the EXISTS subquery), so a row from a scope the record\n // has left is indistinguishable from the live one (#5970). The query-index reader does\n // scope organization_id, but it never sees another organization's row for a record it is\n // indexing, so dropping the column here cannot widen what it returns.\n //\n // This relies on recordId being unique across organizations and tenants for a given\n // entityId \u2014 true of every ORM-backed caller, which passes the record's own primary key.\n // A caller that passed a per-organization natural key \u2014 or a recordId that is only unique\n // WITHIN an organization, as custom_entities_storage's EAV backcompat writes are \u2014 would\n // have one organization's write delete another's live rows. Those callers opt into\n // pinOrganizationId (see SetRecordCustomFieldsOptions) to restore the organization pin.\n //\n // Scope of the delete, precisely: another TENANT's own rows are never touched. Rows with a\n // NULL tenant are, deliberately \u2014 they are instance-global and answer every tenant's reads,\n // so leaving one behind would reproduce the very duplicate this reconciles. Spelled as an\n // explicit $or rather than `$in: [tenantId, null]` because SQL `IN (NULL)` never matches a\n // NULL row.\n const tenantScopeFilter = { $or: [{ tenantId }, { tenantId: null }] }\n const pinOrganizationId = opts.pinOrganizationId === true\n const reconcileScopeFilter = pinOrganizationId\n ? { organizationId, ...tenantScopeFilter }\n : tenantScopeFilter\n const keys = Object.keys(values)\n const presentKeyCount = keys.filter((key) => values[key] !== undefined).length\n if (preferDefs && presentKeyCount > MAX_CUSTOM_FIELD_KEYS_PER_RECORD) {\n throw new Error(TOO_MANY_CUSTOM_FIELDS_ERROR)\n }\n\n // Run the per-key delete+insert work inside ONE database transaction so a\n // multi-value replacement is atomic and isolated. The array branch deletes the\n // existing rows for a key and inserts the replacements; without an enclosing\n // transaction those can land in separate commit boundaries under MikroORM's\n // FlushMode.AUTO (a query elsewhere in the unit auto-flushes part of the work),\n // which intermittently left the field with the delete applied but the inserts\n // missing \u2014 the multi-select EDIT reverted to []. The single commit below makes\n // it all-or-nothing. We only open our own transaction when the caller has not\n // already started one (commands fork the request em and may run setCustomFields\n // outside their own withAtomicFlush tx); join an ambient transaction otherwise.\n const txEm = em as {\n begin?: () => Promise<void>\n commit?: () => Promise<void>\n rollback?: () => Promise<void>\n isInTransaction?: () => boolean\n }\n const txCapable =\n typeof txEm.begin === 'function' &&\n typeof txEm.commit === 'function' &&\n typeof txEm.rollback === 'function' &&\n typeof txEm.isInTransaction === 'function'\n const ownCustomFieldTransaction = txCapable && !txEm.isInTransaction!()\n if (ownCustomFieldTransaction) await txEm.begin!()\n try {\n for (const fieldKey of keys) {\n const raw = values[fieldKey]\n if (raw === undefined) continue\n\n const def = defsByKey?.get(fieldKey)\n const encrypted = Boolean(def?.configJson && (def as any).configJson?.encrypted)\n // A single-value definition never stores an array. One arrives when the detail read\n // returned the duplicate rows #5970 left behind as an array and a form echoed it back\n // unchanged; treating it as a multi-value replacement re-creates every duplicate under\n // the caller's organization (#6468). Collapse it to one value so the scalar branch\n // below reconciles the duplicates away.\n const collapseToSingle = Array.isArray(raw) && Boolean(def) && (def as any).configJson?.multi !== true\n const isArray = Array.isArray(raw) && !collapseToSingle\n // When array (multi-value): replace all existing rows for the key. Delete\n // first, then create replacements, all inside the transaction opened above.\n // Creating rows before a native delete can auto-flush and delete the new\n // values; mixing em.remove(stale) with new rows for the same EAV scope was\n // observed to commit an empty set under MikroORM v7. The explicit order keeps\n // the replacement atomic without letting old-row cleanup target new rows.\n if (isArray) {\n const arr = raw as Primitive[]\n // Reconciling delete (see tenantScopeFilter/reconcileScopeFilter): pinning\n // organizationId here left the rows written under the record's PREVIOUS\n // organization alive next to the replacements, and the Query Engine's value join\n // does not filter organization_id, so both generations answered the same lookup\n // (#5970) \u2014 unless the caller opted into pinOrganizationId because its recordId\n // is not unique across organizations.\n await em.nativeDelete(CustomFieldValue, { entityId, recordId, fieldKey, ...reconcileScopeFilter })\n for (const val of arr) {\n const col: keyof CustomFieldValue = encrypted ? 'valueText' : def ? columnFromKind(def.kind) : columnFromJsValue(val)\n const cf = em.create(CustomFieldValue, { entityId, recordId, organizationId, tenantId, fieldKey, createdAt: new Date() })\n clearValueColumns(cf)\n const stored = encrypted\n ? await encryptCustomFieldValue(val, tenantId, getEncryptionService(), encryptionCache, { entityId, fieldKey })\n : val\n switch (col) {\n case 'valueText': cf.valueText = stored == null ? null : String(stored); break\n case 'valueMultiline': cf.valueMultiline = stored == null ? null : String(stored); break\n case 'valueInt': cf.valueInt = stored == null ? null : Number(stored); break\n case 'valueFloat': cf.valueFloat = stored == null ? null : Number(stored); break\n case 'valueBool': cf.valueBool = stored == null ? null : Boolean(stored); break\n default: cf.valueText = stored == null ? null : String(stored); break\n }\n toPersist.push(cf)\n }\n continue\n }\n\n // Same reconciliation as the multi-value branch (see tenantScopeFilter): load every\n // reachable row for the logical key, reuse the one already in the caller's exact\n // scope and drop the rest, so a row left behind by a previous organization \u2014 and\n // same-scope duplicates from two writes that both missed the lookup \u2014 cannot\n // survive (#5970). Picking the survivor in JS rather than narrowing the query keeps\n // NULL organizations out of SQL three-valued logic, where an inequality predicate\n // would silently spare exactly the stale rows this is meant to remove.\n const existingRows = await em.find(CustomFieldValue, { entityId, recordId, fieldKey, ...reconcileScopeFilter })\n // A soft-deleted row is never the survivor: writing the new value onto it would leave\n // deleted_at set, hiding the field from loadCustomFieldValues while the live row it\n // replaced goes into staleIds. Tombstones still get deleted as stale \u2014 the Query\n // Engine's join does not filter deleted_at, so one left behind would answer reads.\n let cf = existingRows.find((row) => !row.deletedAt\n && (row.organizationId ?? null) === organizationId\n && (row.tenantId ?? null) === tenantId) ?? null\n const value: Primitive = collapseToSingle\n ? pickSingleValue(raw as Primitive[], cf ? await decryptCustomFieldValue(valueFromRow(cf), tenantId, encrypted ? getEncryptionService() : null, encryptionCache, { kind: def?.kind ?? null }) : undefined)\n : raw as Primitive\n const column: keyof CustomFieldValue = encrypted ? 'valueText' : def ? columnFromKind(def.kind) : columnFromJsValue(value)\n const storedValue = encrypted\n ? await encryptCustomFieldValue(value, tenantId, getEncryptionService(), encryptionCache, { entityId, fieldKey })\n : value\n const staleIds = existingRows.filter((row) => row !== cf).map((row) => row.id)\n if (staleIds.length) await em.nativeDelete(CustomFieldValue, { id: { $in: staleIds } })\n if (!cf) {\n cf = em.create(CustomFieldValue, { entityId, recordId, organizationId, tenantId, fieldKey, createdAt: new Date() })\n toPersist.push(cf)\n }\n clearValueColumns(cf)\n switch (column) {\n case 'valueText':\n cf.valueText = (storedValue as Primitive) == null ? null : String(storedValue as Primitive)\n break\n case 'valueMultiline':\n cf.valueMultiline = (storedValue as Primitive) == null ? null : String(storedValue as Primitive)\n break\n case 'valueInt':\n cf.valueInt = (storedValue as Primitive) == null ? null : Number(storedValue as Primitive)\n break\n case 'valueFloat':\n cf.valueFloat = (storedValue as Primitive) == null ? null : Number(storedValue as Primitive)\n break\n case 'valueBool':\n cf.valueBool = (storedValue as Primitive) == null ? null : Boolean(storedValue as Primitive)\n break\n default:\n cf.valueText = (storedValue as Primitive) == null ? null : String(storedValue as Primitive)\n break\n }\n }\n\n if (toPersist.length) em.persist(toPersist)\n await em.flush()\n if (ownCustomFieldTransaction) await txEm.commit!()\n } catch (err) {\n if (ownCustomFieldTransaction) {\n try { await txEm.rollback!() } catch { /* surface the original error, not a rollback failure */ }\n }\n throw err\n }\n // Emit hook for indexing if requested (outside CRUD flows). Runs AFTER the\n // transaction commits so consumers observe the persisted rows.\n try {\n if (typeof opts.onChanged === 'function') {\n await opts.onChanged({ entityId, recordId, organizationId, tenantId })\n }\n } catch {\n // Non-blocking\n }\n}\n"],
|
|
5
|
-
"mappings": "AAEA,SAAS,yBAAyB,yBAAyB,sCAAsC;AACjG;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAyB,wBAAwB;AACjD,SAAS,iCAAiC;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/core'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { decryptCustomFieldValue, encryptCustomFieldValue, resolveTenantEncryptionService } from '@open-mercato/shared/lib/encryption/customFieldValues'\nimport {\n MAX_CUSTOM_FIELD_KEYS_PER_RECORD,\n TOO_MANY_CUSTOM_FIELDS_ERROR,\n} from '@open-mercato/shared/modules/entities/validation'\nimport { CustomFieldDef, CustomFieldValue } from '../data/entities'\nimport { loadScopedCustomFieldDefs } from './scoped-field-defs'\nimport { encryptCustomEntityDocument } from '@open-mercato/shared/lib/encryption/customEntityDoc'\nimport { isTenantDataEncryptionEnabled } from '@open-mercato/shared/lib/encryption/toggles'\n\n/** @internal */\nexport async function encryptCustomEntityRecordDocument(\n em: EntityManager,\n opts: { entityId: string; organizationId: string | null; tenantId: string | null; doc: Record<string, unknown>; encryptionService?: TenantDataEncryptionService | null },\n): Promise<Record<string, unknown>> {\n if (!isTenantDataEncryptionEnabled() || Object.keys(opts.doc).every((key) => key === 'id')) return opts.doc\n const definitions = await loadScopedCustomFieldDefs(em, opts)\n const fields = Array.from(definitions.values(), (definition) => ({\n key: definition.key,\n kind: definition.kind,\n encrypted: Boolean(definition.configJson?.encrypted),\n }))\n return encryptCustomEntityDocument(opts.doc, fields, opts.tenantId, resolveTenantEncryptionService(em, opts.encryptionService))\n}\n\ntype Primitive = string | number | boolean | null | undefined\ntype PrimitiveOrArray = Primitive | Primitive[]\n\nexport type SetRecordCustomFieldsOptions = {\n entityId: string\n recordId: string\n organizationId?: string | null\n tenantId?: string | null\n values: Record<string, PrimitiveOrArray>\n // When true (default), try to use field definitions to decide storage column\n preferDefs?: boolean\n // Optional: notify external systems (e.g., indexing) when values changed\n onChanged?: (payload: { entityId: string; recordId: string; organizationId: string | null; tenantId: string | null }) => Promise<void> | void\n // Optional: re-use an existing tenant encryption service instance\n encryptionService?: TenantDataEncryptionService | null\n // When true, additionally scope the reconciling delete/lookup by organizationId,\n // restoring the pre-#5970 organization pin. Default false relies on `recordId`\n // being unique across organizations for a given entityId \u2014 true for every\n // ORM-backed entity, where recordId is the record's own primary key. That\n // invariant does not hold for `custom_entities_storage` records written under\n // `ENTITIES_BACKCOMPAT_EAV_FOR_CUSTOM`: the table upserts on\n // `(entity_type, entity_id, organization_id)`, so the same id can be a live\n // record in two organizations of one tenant, and an organization-agnostic\n // delete could remove another organization's values (#6034 review). Callers\n // writing those records must pass pinOrganizationId: true.\n pinOrganizationId?: boolean\n}\n\nfunction columnFromKind(kind: string): keyof CustomFieldValue {\n switch (kind) {\n case 'text':\n case 'select':\n case 'currency':\n case 'dictionary':\n case 'phone':\n return 'valueText'\n case 'multiline':\n return 'valueMultiline'\n case 'integer':\n return 'valueInt'\n case 'float':\n return 'valueFloat'\n case 'boolean':\n return 'valueBool'\n default:\n return 'valueText'\n }\n}\n\nfunction columnFromJsValue(v: Primitive): keyof CustomFieldValue {\n if (v === null || v === undefined) return 'valueText'\n if (typeof v === 'boolean') return 'valueBool'\n if (typeof v === 'number') return Number.isInteger(v) ? 'valueInt' : 'valueFloat'\n return 'valueText'\n}\n\n// Clears all value columns to avoid leftovers on update\nfunction clearValueColumns(cf: CustomFieldValue) {\n cf.valueText = null\n cf.valueMultiline = null\n cf.valueInt = null\n cf.valueFloat = null\n cf.valueBool = null\n}\n\nfunction valueFromRow(row: CustomFieldValue): Primitive {\n if (row.valueMultiline != null) return row.valueMultiline\n if (row.valueText != null) return row.valueText\n if (row.valueInt != null) return row.valueInt\n if (row.valueFloat != null) return row.valueFloat\n if (row.valueBool != null) return row.valueBool\n return null\n}\n\n// Keeps the value the caller's own scope already holds when the echoed array contains it,\n// so a save that never touched the field does not swap in another organization's value.\nfunction pickSingleValue(candidates: Primitive[], currentValue: unknown): Primitive {\n if (currentValue != null) {\n const kept = candidates.find((candidate) => candidate != null && String(candidate) === String(currentValue))\n if (kept !== undefined) return kept\n }\n const present = candidates.filter((candidate) => candidate != null)\n return present.length ? present[present.length - 1] : null\n}\n\nexport async function setRecordCustomFields(\n em: EntityManager,\n opts: SetRecordCustomFieldsOptions,\n): Promise<void> {\n const { entityId, recordId, values } = opts\n const organizationId = opts.organizationId ?? null\n const tenantId = opts.tenantId ?? null\n const preferDefs = opts.preferDefs !== false\n\n let defsByKey: Map<string, CustomFieldDef> | undefined\n if (preferDefs) {\n defsByKey = await loadScopedCustomFieldDefs(em, { entityId, organizationId, tenantId })\n }\n\n const toPersist: CustomFieldValue[] = []\n let encryptionService: TenantDataEncryptionService | null | undefined\n const encryptionCache = new Map<string | null, string | null>()\n const getEncryptionService = () => {\n if (encryptionService !== undefined) return encryptionService\n encryptionService = resolveTenantEncryptionService(em as any, opts.encryptionService)\n return encryptionService\n }\n // A write owns every value row a reader in ITS OWN tenant scope could return for this\n // record: same logical key (entityId, recordId, fieldKey), any organization, and either\n // the caller's tenant or the instance-global NULL tenant \u2014 exactly what the Query Engine\n // matches. Organization is left out because the Query Engine's value join does not filter\n // it (engine.ts, the cf join and the EXISTS subquery), so a row from a scope the record\n // has left is indistinguishable from the live one (#5970). The query-index reader does\n // scope organization_id, but it never sees another organization's row for a record it is\n // indexing, so dropping the column here cannot widen what it returns.\n //\n // This relies on recordId being unique across organizations and tenants for a given\n // entityId \u2014 true of every ORM-backed caller, which passes the record's own primary key.\n // A caller that passed a per-organization natural key \u2014 or a recordId that is only unique\n // WITHIN an organization, as custom_entities_storage's EAV backcompat writes are \u2014 would\n // have one organization's write delete another's live rows. Those callers opt into\n // pinOrganizationId (see SetRecordCustomFieldsOptions) to restore the organization pin.\n //\n // Scope of the delete, precisely: another TENANT's own rows are never touched. Rows with a\n // NULL tenant are, deliberately \u2014 they are instance-global and answer every tenant's reads,\n // so leaving one behind would reproduce the very duplicate this reconciles. Spelled as an\n // explicit $or rather than `$in: [tenantId, null]` because SQL `IN (NULL)` never matches a\n // NULL row.\n const tenantScopeFilter = { $or: [{ tenantId }, { tenantId: null }] }\n const pinOrganizationId = opts.pinOrganizationId === true\n const reconcileScopeFilter = pinOrganizationId\n ? { organizationId, ...tenantScopeFilter }\n : tenantScopeFilter\n const keys = Object.keys(values)\n const presentKeyCount = keys.filter((key) => values[key] !== undefined).length\n if (preferDefs && presentKeyCount > MAX_CUSTOM_FIELD_KEYS_PER_RECORD) {\n throw new Error(TOO_MANY_CUSTOM_FIELDS_ERROR)\n }\n\n // Run the per-key delete+insert work inside ONE database transaction so a\n // multi-value replacement is atomic and isolated. The array branch deletes the\n // existing rows for a key and inserts the replacements; without an enclosing\n // transaction those can land in separate commit boundaries under MikroORM's\n // FlushMode.AUTO (a query elsewhere in the unit auto-flushes part of the work),\n // which intermittently left the field with the delete applied but the inserts\n // missing \u2014 the multi-select EDIT reverted to []. The single commit below makes\n // it all-or-nothing. We only open our own transaction when the caller has not\n // already started one (commands fork the request em and may run setCustomFields\n // outside their own withAtomicFlush tx); join an ambient transaction otherwise.\n const txEm = em as {\n begin?: () => Promise<void>\n commit?: () => Promise<void>\n rollback?: () => Promise<void>\n isInTransaction?: () => boolean\n }\n const txCapable =\n typeof txEm.begin === 'function' &&\n typeof txEm.commit === 'function' &&\n typeof txEm.rollback === 'function' &&\n typeof txEm.isInTransaction === 'function'\n const ownCustomFieldTransaction = txCapable && !txEm.isInTransaction!()\n if (ownCustomFieldTransaction) await txEm.begin!()\n try {\n for (const fieldKey of keys) {\n const raw = values[fieldKey]\n if (raw === undefined) continue\n\n const def = defsByKey?.get(fieldKey)\n const encrypted = Boolean(def?.configJson && (def as any).configJson?.encrypted)\n // A single-value definition never stores an array. One arrives when the detail read\n // returned the duplicate rows #5970 left behind as an array and a form echoed it back\n // unchanged; treating it as a multi-value replacement re-creates every duplicate under\n // the caller's organization (#6468). Collapse it to one value so the scalar branch\n // below reconciles the duplicates away.\n const collapseToSingle = Array.isArray(raw) && Boolean(def) && (def as any).configJson?.multi !== true\n const isArray = Array.isArray(raw) && !collapseToSingle\n // When array (multi-value): replace all existing rows for the key. Delete\n // first, then create replacements, all inside the transaction opened above.\n // Creating rows before a native delete can auto-flush and delete the new\n // values; mixing em.remove(stale) with new rows for the same EAV scope was\n // observed to commit an empty set under MikroORM v7. The explicit order keeps\n // the replacement atomic without letting old-row cleanup target new rows.\n if (isArray) {\n const arr = raw as Primitive[]\n // Reconciling delete (see tenantScopeFilter/reconcileScopeFilter): pinning\n // organizationId here left the rows written under the record's PREVIOUS\n // organization alive next to the replacements, and the Query Engine's value join\n // does not filter organization_id, so both generations answered the same lookup\n // (#5970) \u2014 unless the caller opted into pinOrganizationId because its recordId\n // is not unique across organizations.\n await em.nativeDelete(CustomFieldValue, { entityId, recordId, fieldKey, ...reconcileScopeFilter })\n for (const val of arr) {\n const col: keyof CustomFieldValue = encrypted ? 'valueText' : def ? columnFromKind(def.kind) : columnFromJsValue(val)\n const cf = em.create(CustomFieldValue, { entityId, recordId, organizationId, tenantId, fieldKey, createdAt: new Date() })\n clearValueColumns(cf)\n const stored = encrypted\n ? await encryptCustomFieldValue(val, tenantId, getEncryptionService(), encryptionCache, { entityId, fieldKey })\n : val\n switch (col) {\n case 'valueText': cf.valueText = stored == null ? null : String(stored); break\n case 'valueMultiline': cf.valueMultiline = stored == null ? null : String(stored); break\n case 'valueInt': cf.valueInt = stored == null ? null : Number(stored); break\n case 'valueFloat': cf.valueFloat = stored == null ? null : Number(stored); break\n case 'valueBool': cf.valueBool = stored == null ? null : Boolean(stored); break\n default: cf.valueText = stored == null ? null : String(stored); break\n }\n toPersist.push(cf)\n }\n continue\n }\n\n // Same reconciliation as the multi-value branch (see tenantScopeFilter): load every\n // reachable row for the logical key, reuse the one already in the caller's exact\n // scope and drop the rest, so a row left behind by a previous organization \u2014 and\n // same-scope duplicates from two writes that both missed the lookup \u2014 cannot\n // survive (#5970). Picking the survivor in JS rather than narrowing the query keeps\n // NULL organizations out of SQL three-valued logic, where an inequality predicate\n // would silently spare exactly the stale rows this is meant to remove.\n const existingRows = await em.find(CustomFieldValue, { entityId, recordId, fieldKey, ...reconcileScopeFilter })\n // A soft-deleted row is never the survivor: writing the new value onto it would leave\n // deleted_at set, hiding the field from loadCustomFieldValues while the live row it\n // replaced goes into staleIds. Tombstones still get deleted as stale \u2014 the Query\n // Engine's join does not filter deleted_at, so one left behind would answer reads.\n let cf = existingRows.find((row) => !row.deletedAt\n && (row.organizationId ?? null) === organizationId\n && (row.tenantId ?? null) === tenantId) ?? null\n const value: Primitive = collapseToSingle\n ? pickSingleValue(raw as Primitive[], cf ? await decryptCustomFieldValue(valueFromRow(cf), tenantId, encrypted ? getEncryptionService() : null, encryptionCache, { kind: def?.kind ?? null }) : undefined)\n : raw as Primitive\n const column: keyof CustomFieldValue = encrypted ? 'valueText' : def ? columnFromKind(def.kind) : columnFromJsValue(value)\n const storedValue = encrypted\n ? await encryptCustomFieldValue(value, tenantId, getEncryptionService(), encryptionCache, { entityId, fieldKey })\n : value\n const staleIds = existingRows.filter((row) => row !== cf).map((row) => row.id)\n if (staleIds.length) await em.nativeDelete(CustomFieldValue, { id: { $in: staleIds } })\n if (!cf) {\n cf = em.create(CustomFieldValue, { entityId, recordId, organizationId, tenantId, fieldKey, createdAt: new Date() })\n toPersist.push(cf)\n }\n clearValueColumns(cf)\n switch (column) {\n case 'valueText':\n cf.valueText = (storedValue as Primitive) == null ? null : String(storedValue as Primitive)\n break\n case 'valueMultiline':\n cf.valueMultiline = (storedValue as Primitive) == null ? null : String(storedValue as Primitive)\n break\n case 'valueInt':\n cf.valueInt = (storedValue as Primitive) == null ? null : Number(storedValue as Primitive)\n break\n case 'valueFloat':\n cf.valueFloat = (storedValue as Primitive) == null ? null : Number(storedValue as Primitive)\n break\n case 'valueBool':\n cf.valueBool = (storedValue as Primitive) == null ? null : Boolean(storedValue as Primitive)\n break\n default:\n cf.valueText = (storedValue as Primitive) == null ? null : String(storedValue as Primitive)\n break\n }\n }\n\n if (toPersist.length) em.persist(toPersist)\n await em.flush()\n if (ownCustomFieldTransaction) await txEm.commit!()\n } catch (err) {\n if (ownCustomFieldTransaction) {\n try { await txEm.rollback!() } catch { /* surface the original error, not a rollback failure */ }\n }\n throw err\n }\n // Emit hook for indexing if requested (outside CRUD flows). Runs AFTER the\n // transaction commits so consumers observe the persisted rows.\n try {\n if (typeof opts.onChanged === 'function') {\n await opts.onChanged({ entityId, recordId, organizationId, tenantId })\n }\n } catch {\n // Non-blocking\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["cf"]
|
|
7
7
|
}
|