feat(enforcement): record decisions and send notices (#3492)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-15 07:12:46 +02:00
1 parent da1842ecab
commit f5c30fdf8f
383 files changed
+29692 -4139

No files matched your search

+2
View File
@@ -46,6 +46,8 @@ export const AdminACLs = {
BULK_UPDATE_USER_FLAGS: 'bulk:update:user_flags',
DISCOVERY_REMOVE: 'discovery:remove',
DISCOVERY_REVIEW: 'discovery:review',
ENFORCEMENT_ANNOTATE: 'enforcement:annotate',
ENFORCEMENT_VIEW: 'enforcement:view',
GATEWAY_MEMORY_STATS: 'gateway:memory_stats',
GATEWAY_RELOAD_ALL: 'gateway:reload_all',
GIFT_CODES_GENERATE: 'gift_codes:generate',
+7
View File
@@ -67,6 +67,11 @@ export const APIErrorCodes = {
EMAIL_UNAVAILABLE_ON_INSTANCE: 'EMAIL_UNAVAILABLE_ON_INSTANCE',
EMAIL_VERIFICATION_REQUIRED: 'EMAIL_VERIFICATION_REQUIRED',
DIRECT_MESSAGE_EMAIL_VERIFICATION_REQUIRED: 'DIRECT_MESSAGE_EMAIL_VERIFICATION_REQUIRED',
ENFORCEMENT_ACCOUNT_CLOSED: 'ENFORCEMENT_ACCOUNT_CLOSED',
ENFORCEMENT_DECISION_ENDED: 'ENFORCEMENT_DECISION_ENDED',
ENFORCEMENT_DECISION_OWNS_STATE: 'ENFORCEMENT_DECISION_OWNS_STATE',
ENFORCEMENT_DECISION_REPLACED: 'ENFORCEMENT_DECISION_REPLACED',
ENFORCEMENT_DELETION_STARTED: 'ENFORCEMENT_DELETION_STARTED',
FOLLOW_TARGET_CONTENT_WARNING_REQUIRED: 'FOLLOW_TARGET_CONTENT_WARNING_REQUIRED',
FOLLOW_TARGET_NOT_AGE_RESTRICTED: 'FOLLOW_TARGET_NOT_AGE_RESTRICTED',
FRIEND_REQUEST_EMAIL_VERIFICATION_REQUIRED: 'FRIEND_REQUEST_EMAIL_VERIFICATION_REQUIRED',
@@ -144,6 +149,7 @@ export const APIErrorCodes = {
GLOBAL_IP_BANNED: 'GLOBAL_IP_BANNED',
GLOBAL_IP_TEMPORARILY_BANNED: 'GLOBAL_IP_TEMPORARILY_BANNED',
IP_BAN_DECLINED: 'IP_BAN_DECLINED',
IP_BAN_LINKED_TO_DECISION: 'IP_BAN_LINKED_TO_DECISION',
MAX_APPLICATIONS: 'MAX_APPLICATIONS',
MAX_BOOKMARKS: 'MAX_BOOKMARKS',
MAX_ACTIVE_THREADS: 'MAX_ACTIVE_THREADS',
@@ -258,6 +264,7 @@ export const APIErrorCodes = {
UNCLAIMED_ACCOUNT_CANNOT_SUBMIT_REPORTS: 'UNCLAIMED_ACCOUNT_CANNOT_SUBMIT_REPORTS',
UNKNOWN_CHANNEL: 'UNKNOWN_CHANNEL',
UNKNOWN_EMOJI: 'UNKNOWN_EMOJI',
UNKNOWN_ENFORCEMENT_DECISION: 'UNKNOWN_ENFORCEMENT_DECISION',
UNKNOWN_FAVORITE_MEME: 'UNKNOWN_FAVORITE_MEME',
UNKNOWN_FORUM_TAG: 'UNKNOWN_FORUM_TAG',
UNKNOWN_GIFT_CODE: 'UNKNOWN_GIFT_CODE',
+9
View File
@@ -27,4 +27,13 @@ export const DeletionReasons = {
HATE_SPEECH_OR_EXTREMIST_CONTENT: 20,
MALICIOUS_LINKS_OR_MALWARE: 21,
IMPERSONATION_OR_FAKE_IDENTITY: 22,
THREATS_OR_VIOLENT_SPEECH: 23,
VIOLENT_EXTREMISM: 24,
SELF_HARM_PROMOTION: 25,
SEXUAL_CONTENT_PLACEMENT: 26,
ILLEGAL_GOODS_OR_ACTIVITY: 27,
HARMFUL_DECEPTION: 28,
REPORTING_ABUSE: 29,
UNDERAGE_ACCOUNT: 30,
COPYRIGHT: 31,
} as const;
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {DeletionReasons} from '@fluxer/constants/src/Core';
import {
ACCOUNT_DELETION_MIN_DAYS,
ENFORCEMENT_APPEAL_WINDOW_DAYS,
ENFORCEMENT_CLOSURE_MIN_DAYS,
ENFORCEMENT_REASONS,
ENFORCEMENT_RULES,
type EnforcementKind,
type EnforcementLimit,
type EnforcementReasonCode,
isEnforcementKindAllowed,
isEnforcementLimitAllowed,
} from '@fluxer/constants/src/EnforcementReasons';
import {describe, expect, it} from 'vitest';
const ALL_KINDS: ReadonlyArray<EnforcementKind> = [
'warning',
'suspension',
'closure',
'feature_limit',
'content_removal',
'profile_reset',
'community_removal',
];
const ALL_LIMITS: ReadonlyArray<EnforcementLimit> = ['direct_messages', 'reports', 'purchases'];
const RULE_TABLE = [
['spam', 'guidelines', '6-spam-and-service-abuse', true, true],
['service_abuse', 'guidelines', '6-spam-and-service-abuse', true, true],
['raids', 'guidelines', '1-harassment-threats-and-privacy', true, true],
['automation', 'guidelines', '6-spam-and-service-abuse', true, true],
['intimate_images', 'guidelines', '1-harassment-threats-and-privacy', true, false],
['fraud', 'guidelines', '5-illegal-and-dangerous-activity', true, false],
['child_safety', 'guidelines', '4-sexual-content-and-protection-of-minors', true, false],
['privacy', 'guidelines', '1-harassment-threats-and-privacy', true, false],
['harassment', 'guidelines', '1-harassment-threats-and-privacy', true, false],
['payments', 'terms', '4-4-failed-payments-and-disputes', true, false],
['unwanted_sexual_content', 'guidelines', '1-harassment-threats-and-privacy', true, false],
['violent_content', 'guidelines', '3-violence-extremism-and-self-harm', true, true],
['ban_evasion', 'guidelines', '6-spam-and-service-abuse', true, false],
['hate', 'guidelines', '2-hate-and-discrimination', true, false],
['malware', 'guidelines', '5-illegal-and-dangerous-activity', true, false],
['impersonation', 'guidelines', '6-spam-and-service-abuse', true, true],
['threats', 'guidelines', '1-harassment-threats-and-privacy', true, false],
['violent_extremism', 'guidelines', '3-violence-extremism-and-self-harm', true, false],
['self_harm', 'guidelines', '3-violence-extremism-and-self-harm', true, false],
['adult_content', 'guidelines', '4-sexual-content-and-protection-of-minors', true, true],
['illegal_activity', 'guidelines', '5-illegal-and-dangerous-activity', true, false],
['harmful_deception', 'guidelines', '7-harmful-deception-and-manipulated-media', true, false],
['report_abuse', 'guidelines', '8-reporting', true, true],
['age_requirement', 'terms', '1-agreement-and-eligibility', false, false],
['copyright', 'terms', '3-4-copyright', true, true],
] as const;
const CODE_TABLE = [
[3, 'spam', 'standard', 'any'],
[4, 'service_abuse', 'standard', 'any'],
[5, 'raids', 'standard', 'any'],
[6, 'automation', 'standard', 'any'],
[7, 'intimate_images', 'protected', 'any'],
[8, 'fraud', 'standard', 'any'],
[9, 'child_safety', 'restricted', ['suspension', 'content_removal']],
[10, 'privacy', 'protected', 'any'],
[11, 'harassment', 'protected', 'any'],
[12, 'payments', 'standard', ['warning', 'suspension', 'closure', 'feature_limit']],
[13, 'child_safety', 'restricted', ['suspension', 'content_removal']],
[14, 'payments', 'standard', ['warning', 'suspension', 'closure', 'feature_limit']],
[15, 'unwanted_sexual_content', 'protected', 'any'],
[16, 'violent_content', 'standard', 'any'],
[17, 'ban_evasion', 'standard', ['warning', 'suspension', 'closure']],
[18, 'fraud', 'standard', 'any'],
[20, 'hate', 'standard', 'any'],
[21, 'malware', 'standard', 'any'],
[22, 'impersonation', 'standard', 'any'],
[23, 'threats', 'protected', 'any'],
[24, 'violent_extremism', 'restricted', ['suspension', 'content_removal']],
[25, 'self_harm', 'standard', ['warning', 'suspension', 'content_removal', 'profile_reset', 'community_removal']],
[26, 'adult_content', 'standard', 'any'],
[27, 'illegal_activity', 'standard', 'any'],
[28, 'harmful_deception', 'standard', 'any'],
[29, 'report_abuse', 'standard', ['warning', 'feature_limit', 'suspension']],
[30, 'age_requirement', 'standard', ['closure']],
[31, 'copyright', 'standard', ['warning', 'content_removal', 'profile_reset', 'community_removal']],
] as const;
const LIMIT_TABLE: ReadonlyArray<readonly [EnforcementLimit, ReadonlyArray<number>]> = [
['direct_messages', [3, 6, 8, 18, 21]],
['reports', [29]],
['purchases', [12, 14]],
];
const enforcementCodes = Object.keys(ENFORCEMENT_REASONS).map(Number) as Array<EnforcementReasonCode>;
describe('ENFORCEMENT_REASONS', () => {
it('classifies every deletion reason except the three non-enforcement codes', () => {
const expected = Object.values(DeletionReasons).filter(
(code) =>
code !== DeletionReasons.USER_REQUESTED &&
code !== DeletionReasons.OTHER &&
code !== DeletionReasons.INACTIVITY,
);
expect(enforcementCodes.sort((a, b) => a - b)).toEqual(expected.sort((a, b) => a - b));
});
it('matches the code table', () => {
expect(
Object.entries(ENFORCEMENT_REASONS).map(([code, reason]) => [
Number(code),
reason.rule,
reason.handling,
reason.kinds,
]),
).toEqual(CODE_TABLE);
});
it('only names rules that exist', () => {
for (const reason of Object.values(ENFORCEMENT_REASONS)) {
expect(ENFORCEMENT_RULES).toHaveProperty(reason.rule);
}
});
it('limits restricted codes to permanent suspension and content removal', () => {
for (const code of enforcementCodes) {
if (ENFORCEMENT_REASONS[code].handling !== 'restricted') continue;
expect(ALL_KINDS.filter((kind) => isEnforcementKindAllowed(code, kind))).toEqual([
'suspension',
'content_removal',
]);
}
});
});
describe('ENFORCEMENT_RULES', () => {
it('pins policy anchors, the new account bar and network screen visibility', () => {
expect(
Object.entries(ENFORCEMENT_RULES).map(([key, rule]) => [
key,
rule.policy,
rule.anchor,
rule.barsNewAccounts,
rule.showOnNetworkScreen,
]),
).toEqual(RULE_TABLE);
});
});
describe('enforcement limits', () => {
it.each(LIMIT_TABLE)('allows %s only for its codes', (limit, codes) => {
expect(enforcementCodes.filter((code) => isEnforcementLimitAllowed(code, limit))).toEqual(codes);
});
it('allows a feature limit only where some limit applies', () => {
const withFeatureLimit = enforcementCodes.filter((code) => isEnforcementKindAllowed(code, 'feature_limit'));
const withAnyLimit = enforcementCodes.filter((code) =>
ALL_LIMITS.some((limit) => isEnforcementLimitAllowed(code, limit)),
);
expect(withFeatureLimit).toEqual(withAnyLimit);
});
});
describe('enforcement windows', () => {
it('keeps closures open past the appeal window', () => {
expect(ACCOUNT_DELETION_MIN_DAYS).toBe(14);
expect(ENFORCEMENT_APPEAL_WINDOW_DAYS).toBe(60);
expect(ENFORCEMENT_CLOSURE_MIN_DAYS).toBe(ENFORCEMENT_APPEAL_WINDOW_DAYS + ACCOUNT_DELETION_MIN_DAYS);
});
});
@@ -0,0 +1,207 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {DeletionReasons} from '@fluxer/constants/src/Core';
import type {ValueOf} from '@fluxer/constants/src/ValueOf';
export type EnforcementRuleKey =
| 'spam'
| 'service_abuse'
| 'raids'
| 'automation'
| 'intimate_images'
| 'fraud'
| 'child_safety'
| 'privacy'
| 'harassment'
| 'payments'
| 'unwanted_sexual_content'
| 'violent_content'
| 'ban_evasion'
| 'hate'
| 'malware'
| 'impersonation'
| 'threats'
| 'violent_extremism'
| 'self_harm'
| 'adult_content'
| 'illegal_activity'
| 'harmful_deception'
| 'report_abuse'
| 'age_requirement'
| 'copyright';
type EnforcementHandling = 'standard' | 'protected' | 'restricted';
export type EnforcementKind =
| 'warning'
| 'suspension'
| 'closure'
| 'feature_limit'
| 'content_removal'
| 'profile_reset'
| 'community_removal';
export type EnforcementLimit = 'direct_messages' | 'reports' | 'purchases';
export type EnforcementReasonCode = Exclude<
ValueOf<typeof DeletionReasons>,
typeof DeletionReasons.USER_REQUESTED | typeof DeletionReasons.OTHER | typeof DeletionReasons.INACTIVITY
>;
interface EnforcementRule {
policy: 'guidelines' | 'terms';
anchor: string;
barsNewAccounts: boolean;
showOnNetworkScreen: boolean;
}
interface EnforcementReason {
rule: EnforcementRuleKey;
handling: EnforcementHandling;
kinds: ReadonlyArray<EnforcementKind> | 'any';
}
export const ACCOUNT_DELETION_MIN_DAYS = 14;
export const ENFORCEMENT_APPEAL_WINDOW_DAYS = 60;
export const ENFORCEMENT_CLOSURE_MIN_DAYS = ENFORCEMENT_APPEAL_WINDOW_DAYS + ACCOUNT_DELETION_MIN_DAYS;
const HARASSMENT_ANCHOR = '1-harassment-threats-and-privacy';
const VIOLENCE_ANCHOR = '3-violence-extremism-and-self-harm';
const SEXUAL_CONTENT_ANCHOR = '4-sexual-content-and-protection-of-minors';
const ILLEGAL_ACTIVITY_ANCHOR = '5-illegal-and-dangerous-activity';
const SERVICE_ABUSE_ANCHOR = '6-spam-and-service-abuse';
function guidelinesRule(anchor: string, showOnNetworkScreen: boolean): EnforcementRule {
return {policy: 'guidelines', anchor, barsNewAccounts: true, showOnNetworkScreen};
}
export const ENFORCEMENT_RULES: Readonly<Record<EnforcementRuleKey, EnforcementRule>> = {
spam: guidelinesRule(SERVICE_ABUSE_ANCHOR, true),
service_abuse: guidelinesRule(SERVICE_ABUSE_ANCHOR, true),
raids: guidelinesRule(HARASSMENT_ANCHOR, true),
automation: guidelinesRule(SERVICE_ABUSE_ANCHOR, true),
intimate_images: guidelinesRule(HARASSMENT_ANCHOR, false),
fraud: guidelinesRule(ILLEGAL_ACTIVITY_ANCHOR, false),
child_safety: guidelinesRule(SEXUAL_CONTENT_ANCHOR, false),
privacy: guidelinesRule(HARASSMENT_ANCHOR, false),
harassment: guidelinesRule(HARASSMENT_ANCHOR, false),
payments: {
policy: 'terms',
anchor: '4-4-failed-payments-and-disputes',
barsNewAccounts: true,
showOnNetworkScreen: false,
},
unwanted_sexual_content: guidelinesRule(HARASSMENT_ANCHOR, false),
violent_content: guidelinesRule(VIOLENCE_ANCHOR, true),
ban_evasion: guidelinesRule(SERVICE_ABUSE_ANCHOR, false),
hate: guidelinesRule('2-hate-and-discrimination', false),
malware: guidelinesRule(ILLEGAL_ACTIVITY_ANCHOR, false),
impersonation: guidelinesRule(SERVICE_ABUSE_ANCHOR, true),
threats: guidelinesRule(HARASSMENT_ANCHOR, false),
violent_extremism: guidelinesRule(VIOLENCE_ANCHOR, false),
self_harm: guidelinesRule(VIOLENCE_ANCHOR, false),
adult_content: guidelinesRule(SEXUAL_CONTENT_ANCHOR, true),
illegal_activity: guidelinesRule(ILLEGAL_ACTIVITY_ANCHOR, false),
harmful_deception: guidelinesRule('7-harmful-deception-and-manipulated-media', false),
report_abuse: guidelinesRule('8-reporting', true),
age_requirement: {
policy: 'terms',
anchor: '1-agreement-and-eligibility',
barsNewAccounts: false,
showOnNetworkScreen: false,
},
copyright: {policy: 'terms', anchor: '3-4-copyright', barsNewAccounts: true, showOnNetworkScreen: true},
};
const RESTRICTED_KINDS: ReadonlyArray<EnforcementKind> = ['suspension', 'content_removal'];
const PAYMENT_KINDS: ReadonlyArray<EnforcementKind> = ['warning', 'suspension', 'closure', 'feature_limit'];
export const ENFORCEMENT_REASONS: Readonly<Record<EnforcementReasonCode, EnforcementReason>> = {
[DeletionReasons.SPAM]: {rule: 'spam', handling: 'standard', kinds: 'any'},
[DeletionReasons.CHEATING_OR_EXPLOITATION]: {rule: 'service_abuse', handling: 'standard', kinds: 'any'},
[DeletionReasons.COORDINATED_RAIDING]: {rule: 'raids', handling: 'standard', kinds: 'any'},
[DeletionReasons.AUTOMATION_OR_SELFBOT]: {rule: 'automation', handling: 'standard', kinds: 'any'},
[DeletionReasons.NONCONSENSUAL_SEXUAL_CONTENT]: {rule: 'intimate_images', handling: 'protected', kinds: 'any'},
[DeletionReasons.SCAM_OR_SOCIAL_ENGINEERING]: {rule: 'fraud', handling: 'standard', kinds: 'any'},
[DeletionReasons.CHILD_SEXUAL_CONTENT]: {rule: 'child_safety', handling: 'restricted', kinds: RESTRICTED_KINDS},
[DeletionReasons.PRIVACY_VIOLATION_OR_DOXXING]: {rule: 'privacy', handling: 'protected', kinds: 'any'},
[DeletionReasons.HARASSMENT_OR_BULLYING]: {rule: 'harassment', handling: 'protected', kinds: 'any'},
[DeletionReasons.PAYMENT_FRAUD]: {rule: 'payments', handling: 'standard', kinds: PAYMENT_KINDS},
[DeletionReasons.CHILD_SAFETY_VIOLATION]: {rule: 'child_safety', handling: 'restricted', kinds: RESTRICTED_KINDS},
[DeletionReasons.BILLING_DISPUTE_OR_ABUSE]: {rule: 'payments', handling: 'standard', kinds: PAYMENT_KINDS},
[DeletionReasons.UNSOLICITED_EXPLICIT_CONTENT]: {
rule: 'unwanted_sexual_content',
handling: 'protected',
kinds: 'any',
},
[DeletionReasons.GRAPHIC_VIOLENCE]: {rule: 'violent_content', handling: 'standard', kinds: 'any'},
[DeletionReasons.BAN_EVASION]: {
rule: 'ban_evasion',
handling: 'standard',
kinds: ['warning', 'suspension', 'closure'],
},
[DeletionReasons.TOKEN_OR_CREDENTIAL_SCAM]: {rule: 'fraud', handling: 'standard', kinds: 'any'},
[DeletionReasons.HATE_SPEECH_OR_EXTREMIST_CONTENT]: {rule: 'hate', handling: 'standard', kinds: 'any'},
[DeletionReasons.MALICIOUS_LINKS_OR_MALWARE]: {rule: 'malware', handling: 'standard', kinds: 'any'},
[DeletionReasons.IMPERSONATION_OR_FAKE_IDENTITY]: {rule: 'impersonation', handling: 'standard', kinds: 'any'},
[DeletionReasons.THREATS_OR_VIOLENT_SPEECH]: {rule: 'threats', handling: 'protected', kinds: 'any'},
[DeletionReasons.VIOLENT_EXTREMISM]: {rule: 'violent_extremism', handling: 'restricted', kinds: RESTRICTED_KINDS},
[DeletionReasons.SELF_HARM_PROMOTION]: {
rule: 'self_harm',
handling: 'standard',
kinds: ['warning', 'suspension', 'content_removal', 'profile_reset', 'community_removal'],
},
[DeletionReasons.SEXUAL_CONTENT_PLACEMENT]: {rule: 'adult_content', handling: 'standard', kinds: 'any'},
[DeletionReasons.ILLEGAL_GOODS_OR_ACTIVITY]: {rule: 'illegal_activity', handling: 'standard', kinds: 'any'},
[DeletionReasons.HARMFUL_DECEPTION]: {rule: 'harmful_deception', handling: 'standard', kinds: 'any'},
[DeletionReasons.REPORTING_ABUSE]: {
rule: 'report_abuse',
handling: 'standard',
kinds: ['warning', 'feature_limit', 'suspension'],
},
[DeletionReasons.UNDERAGE_ACCOUNT]: {rule: 'age_requirement', handling: 'standard', kinds: ['closure']},
[DeletionReasons.COPYRIGHT]: {
rule: 'copyright',
handling: 'standard',
kinds: ['warning', 'content_removal', 'profile_reset', 'community_removal'],
},
};
const ENFORCEMENT_SPAMMER_LIMIT_RULES: ReadonlySet<EnforcementRuleKey> = new Set([
'spam',
'automation',
'fraud',
'malware',
]);
const ENFORCEMENT_LIMIT_RULES: Readonly<Record<EnforcementLimit, ReadonlySet<EnforcementRuleKey>>> = {
direct_messages: ENFORCEMENT_SPAMMER_LIMIT_RULES,
reports: new Set(['report_abuse']),
purchases: new Set(['payments']),
};
const ACCOUNT_KINDS: ReadonlySet<EnforcementKind> = new Set(['suspension', 'closure']);
export function isAccountKind(kind: EnforcementKind): boolean {
return ACCOUNT_KINDS.has(kind);
}
export function isEnforcementReasonCode(code: number | null | undefined): code is EnforcementReasonCode {
return code !== null && code !== undefined && Object.hasOwn(ENFORCEMENT_REASONS, code);
}
export function enforcementReasonOf(code: number | null | undefined): EnforcementReason | null {
return isEnforcementReasonCode(code) ? ENFORCEMENT_REASONS[code] : null;
}
const ENFORCEMENT_LIMITS = Object.keys(ENFORCEMENT_LIMIT_RULES) as ReadonlyArray<EnforcementLimit>;
export function isEnforcementLimitAllowed(code: EnforcementReasonCode, limit: EnforcementLimit): boolean {
return ENFORCEMENT_LIMIT_RULES[limit].has(ENFORCEMENT_REASONS[code].rule);
}
export function isEnforcementKindAllowed(code: EnforcementReasonCode, kind: EnforcementKind): boolean {
const {kinds} = ENFORCEMENT_REASONS[code];
if (kinds !== 'any' && !kinds.includes(kind)) return false;
return kind !== 'feature_limit' || ENFORCEMENT_LIMITS.some((limit) => isEnforcementLimitAllowed(code, limit));
}
+24 -1
View File
@@ -10,7 +10,6 @@ export const ValidationErrorCodes = {
ALL_CHANNELS_MUST_BELONG_TO_GUILD: 'ALL_CHANNELS_MUST_BELONG_TO_GUILD',
ANIMATED_AVATARS_REQUIRE_PREMIUM: 'ANIMATED_AVATARS_REQUIRE_PREMIUM',
ANIMATED_GUILD_BANNER_REQUIRES_FEATURE: 'ANIMATED_GUILD_BANNER_REQUIRES_FEATURE',
AT_LEAST_ONE_ENTRY_IS_REQUIRED: 'AT_LEAST_ONE_ENTRY_IS_REQUIRED',
AT_LEAST_ONE_RECIPIENT_REQUIRED: 'AT_LEAST_ONE_RECIPIENT_REQUIRED',
ATTACHMENT_ID_NOT_FOUND_IN_MESSAGE: 'ATTACHMENT_ID_NOT_FOUND_IN_MESSAGE',
ATTACHMENT_MUST_BE_IMAGE: 'ATTACHMENT_MUST_BE_IMAGE',
@@ -72,6 +71,27 @@ export const ValidationErrorCodes = {
EMAIL_TOKEN_EXPIRED: 'EMAIL_TOKEN_EXPIRED',
EMAIL_DOMAIN_CANNOT_RECEIVE_MAIL: 'EMAIL_DOMAIN_CANNOT_RECEIVE_MAIL',
EMAIL_DOMAIN_NOT_ALLOWED_FOR_SSO: 'EMAIL_DOMAIN_NOT_ALLOWED_FOR_SSO',
ENFORCEMENT_CONFIRMATIONS_REQUIRED: 'ENFORCEMENT_CONFIRMATIONS_REQUIRED',
ENFORCEMENT_EVIDENCE_DM_UNREPORTED: 'ENFORCEMENT_EVIDENCE_DM_UNREPORTED',
ENFORCEMENT_EVIDENCE_NOT_ALLOWED: 'ENFORCEMENT_EVIDENCE_NOT_ALLOWED',
ENFORCEMENT_EVIDENCE_NOT_FOUND: 'ENFORCEMENT_EVIDENCE_NOT_FOUND',
ENFORCEMENT_EVIDENCE_NOT_SUBJECT: 'ENFORCEMENT_EVIDENCE_NOT_SUBJECT',
ENFORCEMENT_EVIDENCE_REQUIRED: 'ENFORCEMENT_EVIDENCE_REQUIRED',
ENFORCEMENT_FACTS_REQUIRED: 'ENFORCEMENT_FACTS_REQUIRED',
ENFORCEMENT_INTERNAL_NOTE_REQUIRED: 'ENFORCEMENT_INTERNAL_NOTE_REQUIRED',
ENFORCEMENT_KIND_NOT_ALLOWED: 'ENFORCEMENT_KIND_NOT_ALLOWED',
ENFORCEMENT_LIMIT_END_INVALID: 'ENFORCEMENT_LIMIT_END_INVALID',
ENFORCEMENT_LIMIT_NOT_ALLOWED: 'ENFORCEMENT_LIMIT_NOT_ALLOWED',
ENFORCEMENT_NETWORK_BLOCK_NOT_ALLOWED: 'ENFORCEMENT_NETWORK_BLOCK_NOT_ALLOWED',
ENFORCEMENT_NETWORK_SHARED: 'ENFORCEMENT_NETWORK_SHARED',
ENFORCEMENT_NETWORK_UNKNOWN: 'ENFORCEMENT_NETWORK_UNKNOWN',
ENFORCEMENT_OUTCOME_NOT_ALLOWED: 'ENFORCEMENT_OUTCOME_NOT_ALLOWED',
ENFORCEMENT_PERMANENT_REQUIRED: 'ENFORCEMENT_PERMANENT_REQUIRED',
ENFORCEMENT_REPORT_MISMATCH: 'ENFORCEMENT_REPORT_MISMATCH',
ENFORCEMENT_REPORT_REQUIRED: 'ENFORCEMENT_REPORT_REQUIRED',
ENFORCEMENT_RIGHTS_HOLDER_NOT_ALLOWED: 'ENFORCEMENT_RIGHTS_HOLDER_NOT_ALLOWED',
ENFORCEMENT_RIGHTS_HOLDER_REQUIRED: 'ENFORCEMENT_RIGHTS_HOLDER_REQUIRED',
ENFORCEMENT_STATEMENT_NOT_ALLOWED: 'ENFORCEMENT_STATEMENT_NOT_ALLOWED',
EMBED_INDEX_OUT_OF_BOUNDS: 'EMBED_INDEX_OUT_OF_BOUNDS',
EMBED_SPLASH_REQUIRES_FEATURE: 'EMBED_SPLASH_REQUIRES_FEATURE',
EMBEDS_EXCEED_MAX_CHARACTERS: 'EMBEDS_EXCEED_MAX_CHARACTERS',
@@ -83,6 +103,7 @@ export const ValidationErrorCodes = {
FAVORITE_MEME_NOT_FOUND: 'FAVORITE_MEME_NOT_FOUND',
FILE_INDEX_EXCEEDS_MAXIMUM: 'FILE_INDEX_EXCEEDS_MAXIMUM',
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
FLAG_REQUIRES_ENFORCEMENT_ROUTE: 'FLAG_REQUIRES_ENFORCEMENT_ROUTE',
FILENAME_LENGTH_INVALID: 'FILENAME_LENGTH_INVALID',
FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT: 'FORWARD_MESSAGES_CANNOT_CONTAIN_CONTENT',
FORWARD_REFERENCE_REQUIRES_CHANNEL_AND_MESSAGE: 'FORWARD_REFERENCE_REQUIRES_CHANNEL_AND_MESSAGE',
@@ -149,6 +170,7 @@ export const ValidationErrorCodes = {
MESSAGE_IDS_CANNOT_BE_EMPTY: 'MESSAGE_IDS_CANNOT_BE_EMPTY',
MESSAGES_ARRAY_REQUIRED_AND_NOT_EMPTY: 'MESSAGES_ARRAY_REQUIRED_AND_NOT_EMPTY',
MESSAGES_WITH_SNAPSHOTS_CANNOT_BE_EDITED: 'MESSAGES_WITH_SNAPSHOTS_CANNOT_BE_EDITED',
MODERATION_BASIS_REQUIRED: 'MODERATION_BASIS_REQUIRED',
MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED: 'MULTIPLE_FILES_FOR_INDEX_NOT_ALLOWED',
MUST_AGREE_TO_TOS_AND_PRIVACY_POLICY: 'MUST_AGREE_TO_TOS_AND_PRIVACY_POLICY',
MUST_BE_MINIMUM_AGE: 'MUST_BE_MINIMUM_AGE',
@@ -180,6 +202,7 @@ export const ValidationErrorCodes = {
RECIPIENT_IDS_MUST_BE_STRINGS: 'RECIPIENT_IDS_MUST_BE_STRINGS',
RECIPIENT_IDS_MUST_BE_VALID_SNOWFLAKES: 'RECIPIENT_IDS_MUST_BE_VALID_SNOWFLAKES',
REFERENCED_ATTACHMENT_NOT_FOUND: 'REFERENCED_ATTACHMENT_NOT_FOUND',
REPORT_OUTCOME_INVALID: 'REPORT_OUTCOME_INVALID',
SESSION_TIMEOUT: 'SESSION_TIMEOUT',
SETUP_TOKEN_INVALID: 'SETUP_TOKEN_INVALID',
SETUP_TOKEN_REQUIRED: 'SETUP_TOKEN_REQUIRED',