const FEATURE_FLAG_KEYS = { aiAssist: "ai_assist", erpSync: "erp_sync", invoiceVerification: "invoice_verification", leaveHolidayCalendar: "leave_holiday_calendar", budgetPrecheck: "budget_precheck_v1", reimbursementInvoiceGuard: "reimbursement_invoice_guard_v1", paymentBatch: "payment_batch_v1", portalHome: "portal_home_v1", globalSearch: "global_search_v1", announcements: "announcements_v1", workflowDefinitionAdmin: "workflow_definition_admin_v1", cutoverConsole: "cutover_console_v1", presenceBoard: "presence_board_v1", attendance: "attendance_v1", attendanceUnified: "attendance_unified_v1", weeklyReport: "weekly_report_v1", employeeLifecycle: "employee_lifecycle_v1", hrWecomDirectorySync: "hr_wecom_directory_sync_v1", wecomHrMasterSync: "wecom_hr_master_sync_v1", todoBoard: "todo_board_v1", wecomEntry: "wecom_entry_v1", projectDomain: "project_domain_v1", projectLinking: "project_linking_v1", projectCommunicationLog: "project_communication_log_v1", projectClosureChecklist: "project_closure_checklist_v1", confidentialDocRefs: "confidential_doc_refs_v1", publicSecurityHardening: "public_security_hardening_v1", }; const FEATURE_FLAG_KEY_LIST = Object.freeze(Object.values(FEATURE_FLAG_KEYS)); const OVERRIDE_MODE_LIST = Object.freeze(["inherit", "force_on", "force_off"]); const ROLLOUT_TYPE_LIST = Object.freeze(["global", "department_allowlist"]); function isKnownFeatureFlagKey(flagKey) { return FEATURE_FLAG_KEY_LIST.includes(flagKey); } function normalizeOverrideMode(value) { const mode = String(value ?? "force_off").trim().toLowerCase(); if (OVERRIDE_MODE_LIST.includes(mode)) { return mode; } return "force_off"; } function normalizeRolloutType(value) { const type = String(value ?? "global").trim().toLowerCase(); if (ROLLOUT_TYPE_LIST.includes(type)) { return type; } return "global"; } function sanitizeDepartmentIds(value) { if (!Array.isArray(value)) { return []; } const seen = new Set(); const items = []; for (const id of value) { if (typeof id !== "string") { continue; } const trimmed = id.trim(); if (!trimmed) { continue; } if (!seen.has(trimmed)) { seen.add(trimmed); items.push(trimmed); } } return items; } function resolveEnvGate(config, flagKey) { if (flagKey === FEATURE_FLAG_KEYS.aiAssist) { return Boolean(config.aiAssistEnabled); } if (flagKey === FEATURE_FLAG_KEYS.erpSync) { return Boolean(config.erpSyncEnabled); } if (flagKey === FEATURE_FLAG_KEYS.invoiceVerification) { return Boolean(config.invoiceVerificationEnabled); } if (flagKey === FEATURE_FLAG_KEYS.leaveHolidayCalendar) { return Boolean(config.leaveHolidayCalendarEnabled); } if (flagKey === FEATURE_FLAG_KEYS.budgetPrecheck) { return Boolean(config.budgetPrecheckEnabled); } if (flagKey === FEATURE_FLAG_KEYS.reimbursementInvoiceGuard) { return Boolean(config.reimbursementInvoiceGuardEnabled); } if (flagKey === FEATURE_FLAG_KEYS.paymentBatch) { return Boolean(config.paymentBatchEnabled); } if (flagKey === FEATURE_FLAG_KEYS.portalHome) { return Boolean(config.portalHomeEnabled); } if (flagKey === FEATURE_FLAG_KEYS.globalSearch) { return Boolean(config.globalSearchEnabled); } if (flagKey === FEATURE_FLAG_KEYS.announcements) { return Boolean(config.announcementsEnabled); } if (flagKey === FEATURE_FLAG_KEYS.workflowDefinitionAdmin) { return Boolean(config.workflowDefinitionAdminEnabled); } if (flagKey === FEATURE_FLAG_KEYS.cutoverConsole) { return Boolean(config.cutoverConsoleEnabled); } if (flagKey === FEATURE_FLAG_KEYS.presenceBoard) { return Boolean(config.presenceBoardEnabled); } if (flagKey === FEATURE_FLAG_KEYS.attendance) { return false; } if (flagKey === FEATURE_FLAG_KEYS.attendanceUnified) { return Boolean(config.attendanceUnifiedEnabled); } if (flagKey === FEATURE_FLAG_KEYS.weeklyReport) { return Boolean(config.weeklyReportEnabled); } if (flagKey === FEATURE_FLAG_KEYS.employeeLifecycle) { return Boolean(config.employeeLifecycleEnabled); } if (flagKey === FEATURE_FLAG_KEYS.hrWecomDirectorySync) { return Boolean(config.wecomContactSyncEnabled); } if (flagKey === FEATURE_FLAG_KEYS.wecomHrMasterSync) { return Boolean(config.wecomHrMasterSyncEnabled); } if (flagKey === FEATURE_FLAG_KEYS.todoBoard) { return Boolean(config.todoBoardEnabled); } if (flagKey === FEATURE_FLAG_KEYS.wecomEntry) { return Boolean(config.wecomEntryEnabled); } if (flagKey === FEATURE_FLAG_KEYS.projectDomain) { return Boolean(config.projectDomainEnabled); } if (flagKey === FEATURE_FLAG_KEYS.projectLinking) { return Boolean(config.projectLinkingEnabled); } if (flagKey === FEATURE_FLAG_KEYS.projectCommunicationLog) { return Boolean(config.projectCommunicationLogEnabled); } if (flagKey === FEATURE_FLAG_KEYS.projectClosureChecklist) { return Boolean(config.projectClosureChecklistEnabled); } if (flagKey === FEATURE_FLAG_KEYS.confidentialDocRefs) { return Boolean(config.confidentialDocRefsEnabled); } if (flagKey === FEATURE_FLAG_KEYS.publicSecurityHardening) { return Boolean(config.publicSecurityHardeningEnabled); } return false; } function mapFlagRecord(record) { if (!record) { return null; } const departmentIds = sanitizeDepartmentIds(record.department_ids); const overrideMode = normalizeOverrideMode(record.override_mode); const rolloutType = normalizeRolloutType(record.rollout_type); return { id: record.id, flagKey: record.flag_key, overrideMode, rolloutType, departmentIds, note: record.note ?? "", version: Number(record.version ?? 1), createdAt: record.created_at?.toISOString?.() ?? null, createdBy: record.created_by ?? null, updatedAt: record.updated_at?.toISOString?.() ?? null, updatedBy: record.updated_by ?? null, }; } function buildDefaultRecord(flagKey) { return { id: null, flagKey, overrideMode: "force_off", rolloutType: "global", departmentIds: [], note: "", version: 1, createdAt: null, createdBy: null, updatedAt: null, updatedBy: null, }; } function withEvaluation(base, evaluation) { return { ...base, envEnabled: evaluation.envEnabled, effectiveEnabled: evaluation.enabled, effectiveReason: evaluation.reason, matchedDepartmentId: evaluation.matchedDepartmentId, requiresDepartment: evaluation.requiresDepartment, }; } async function getFlagRecord(prisma, flagKey) { const row = await prisma.featureFlag.findUnique({ where: { flag_key: flagKey }, }); return mapFlagRecord(row); } async function evaluateFeatureFlag({ prisma, config, flagKey, departmentId = null, strictDepartment = true, }) { const envEnabled = resolveEnvGate(config, flagKey); const normalizedDepartmentId = typeof departmentId === "string" && departmentId.trim() ? departmentId.trim() : null; if (!envEnabled) { return { flagKey, enabled: false, reason: "env_hard_off", envEnabled, matchedDepartmentId: null, requiresDepartment: false, }; } const record = await getFlagRecord(prisma, flagKey); if (!record) { return { flagKey, enabled: false, reason: "missing_flag_record", envEnabled, matchedDepartmentId: null, requiresDepartment: false, }; } if (!OVERRIDE_MODE_LIST.includes(record.overrideMode)) { return { flagKey, enabled: false, reason: "invalid_override_mode", envEnabled, matchedDepartmentId: null, requiresDepartment: false, }; } if (!ROLLOUT_TYPE_LIST.includes(record.rolloutType)) { return { flagKey, enabled: false, reason: "invalid_rollout_type", envEnabled, matchedDepartmentId: null, requiresDepartment: false, }; } if (record.overrideMode === "force_off") { return { flagKey, enabled: false, reason: "flag_force_off", envEnabled, matchedDepartmentId: null, requiresDepartment: false, }; } const modeEnabled = record.overrideMode === "force_on" || record.overrideMode === "inherit"; if (!modeEnabled) { return { flagKey, enabled: false, reason: "mode_not_enabled", envEnabled, matchedDepartmentId: null, requiresDepartment: false, }; } if (record.rolloutType === "global") { return { flagKey, enabled: true, reason: record.overrideMode === "inherit" ? "env_inherit_global_on" : "flag_force_on_global", envEnabled, matchedDepartmentId: null, requiresDepartment: false, }; } const allowlist = record.departmentIds; if (!allowlist.length) { return { flagKey, enabled: false, reason: "department_allowlist_empty", envEnabled, matchedDepartmentId: null, requiresDepartment: true, }; } if (!normalizedDepartmentId) { if (strictDepartment) { return { flagKey, enabled: false, reason: "department_missing", envEnabled, matchedDepartmentId: null, requiresDepartment: true, }; } return { flagKey, enabled: true, reason: "department_deferred", envEnabled, matchedDepartmentId: null, requiresDepartment: true, }; } const matched = allowlist.includes(normalizedDepartmentId); if (!matched) { return { flagKey, enabled: false, reason: "department_not_allowed", envEnabled, matchedDepartmentId: normalizedDepartmentId, requiresDepartment: true, }; } return { flagKey, enabled: true, reason: record.overrideMode === "inherit" ? "env_inherit_department_allowed" : "flag_force_on_department_allowed", envEnabled, matchedDepartmentId: normalizedDepartmentId, requiresDepartment: true, }; } async function listFeatureFlags(prisma, config) { const rows = await prisma.featureFlag.findMany({ where: { flag_key: { in: FEATURE_FLAG_KEY_LIST }, }, orderBy: { flag_key: "asc" }, }); const recordMap = new Map(rows.map((row) => [row.flag_key, mapFlagRecord(row)])); const items = []; for (const flagKey of FEATURE_FLAG_KEY_LIST) { const base = recordMap.get(flagKey) ?? buildDefaultRecord(flagKey); const evaluation = await evaluateFeatureFlag({ prisma, config, flagKey, departmentId: null, strictDepartment: false, }); items.push(withEvaluation(base, evaluation)); } return items; } async function updateFeatureFlag(prisma, input) { const flagKey = String(input.flagKey ?? "").trim(); if (!isKnownFeatureFlagKey(flagKey)) { return { status: "not_found", current: null, updated: null, }; } const overrideMode = normalizeOverrideMode(input.overrideMode); const rolloutType = normalizeRolloutType(input.rolloutType); const departmentIds = rolloutType === "department_allowlist" ? sanitizeDepartmentIds(input.departmentIds) : []; return prisma.$transaction(async (tx) => { const currentRow = await tx.featureFlag.findUnique({ where: { flag_key: flagKey }, }); if (!currentRow) { return { status: "not_found", current: null, updated: null, }; } const current = mapFlagRecord(currentRow); if (Number(input.expectedVersion) !== current.version) { return { status: "version_conflict", current, updated: null, }; } const updateResult = await tx.featureFlag.updateMany({ where: { id: current.id, version: current.version, }, data: { override_mode: overrideMode, rollout_type: rolloutType, department_ids: departmentIds, note: input.note ?? "", version: { increment: 1, }, updated_by: input.actorId ?? null, }, }); if (updateResult.count !== 1) { const latest = await tx.featureFlag.findUnique({ where: { flag_key: flagKey }, }); return { status: "version_conflict", current: mapFlagRecord(latest), updated: null, }; } const updatedRow = await tx.featureFlag.findUnique({ where: { flag_key: flagKey }, }); return { status: "updated", current, updated: mapFlagRecord(updatedRow), }; }); } module.exports = { FEATURE_FLAG_KEYS, FEATURE_FLAG_KEY_LIST, OVERRIDE_MODE_LIST, ROLLOUT_TYPE_LIST, isKnownFeatureFlagKey, sanitizeDepartmentIds, evaluateFeatureFlag, listFeatureFlags, updateFeatureFlag, };