@helyx/bot 1.1.0 → 1.2.1
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/CHANGELOG.md +71 -0
- package/README.md +16 -1
- package/dist/auto-moderation-scanner-registry.d.ts +27 -0
- package/dist/auto-moderation-scanner-registry.js +172 -0
- package/dist/cloud-connector.js +3 -0
- package/dist/cloud-control-dispatcher.js +32 -0
- package/dist/command-path-contract.js +1 -0
- package/dist/control-permissions.d.ts +2 -2
- package/dist/control-permissions.js +4 -2
- package/dist/control-server.d.ts +72 -16
- package/dist/control-server.js +150 -250
- package/dist/dashboard-action-control.d.ts +25 -0
- package/dist/dashboard-action-control.js +73 -0
- package/dist/dashboard-action-routes.d.ts +9 -0
- package/dist/dashboard-action-routes.js +36 -0
- package/dist/dashboard-feature-control.d.ts +37 -0
- package/dist/dashboard-feature-control.js +206 -0
- package/dist/dashboard-module-projection.d.ts +9 -0
- package/dist/dashboard-module-projection.js +15 -0
- package/dist/dashboard-preview-import-routes.d.ts +6 -0
- package/dist/dashboard-preview-import-routes.js +121 -0
- package/dist/discord-analytics.d.ts +3 -0
- package/dist/discord-analytics.js +49 -5
- package/dist/discord-auto-moderation-service.d.ts +21 -0
- package/dist/discord-auto-moderation-service.js +425 -0
- package/dist/discord-guild-health.d.ts +2 -2
- package/dist/discord-member-enforcement-service.d.ts +26 -0
- package/dist/discord-member-enforcement-service.js +590 -0
- package/dist/discord-message-enforcement-service.d.ts +9 -0
- package/dist/discord-message-enforcement-service.js +102 -0
- package/dist/discord-violation-thread-service.d.ts +33 -0
- package/dist/discord-violation-thread-service.js +789 -0
- package/dist/ecko-evidence-receiver.d.ts +32 -0
- package/dist/ecko-evidence-receiver.js +173 -0
- package/dist/ecko-transport-auth.d.ts +19 -0
- package/dist/ecko-transport-auth.js +80 -0
- package/dist/ecko-transport-routes.d.ts +19 -0
- package/dist/ecko-transport-routes.js +130 -0
- package/dist/embedded-auto-moderation-pool.d.ts +46 -0
- package/dist/embedded-auto-moderation-pool.js +324 -0
- package/dist/embedded-auto-moderation-scanner.d.ts +34 -0
- package/dist/embedded-auto-moderation-scanner.js +263 -0
- package/dist/embedded-auto-moderation-worker-runtime.d.ts +37 -0
- package/dist/embedded-auto-moderation-worker-runtime.js +37 -0
- package/dist/embedded-auto-moderation-worker.d.ts +2 -0
- package/dist/embedded-auto-moderation-worker.js +13 -0
- package/dist/events.d.ts +5 -1
- package/dist/events.js +243 -4
- package/dist/guilds.d.ts +2 -2
- package/dist/guilds.js +14 -8
- package/dist/hosted-server-moderation.d.ts +16 -5
- package/dist/hosted-server-moderation.js +32 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +84 -14
- package/dist/moderation-action-registry.d.ts +11 -0
- package/dist/moderation-action-registry.js +375 -0
- package/dist/moderation-case-disassociation.d.ts +13 -0
- package/dist/moderation-case-disassociation.js +83 -0
- package/dist/moderation-platform-services.d.ts +27 -0
- package/dist/moderation-platform-services.js +50 -0
- package/dist/modules.d.ts +6 -0
- package/dist/modules.js +65 -3
- package/dist/privileged-message-metadata-cache.d.ts +35 -0
- package/dist/privileged-message-metadata-cache.js +70 -0
- package/dist/scheduled-task-runner.js +24 -1
- package/dist/shard-contracts.d.ts +151 -1
- package/dist/shard-contracts.js +142 -1
- package/dist/shard-coordinator.d.ts +1 -1
- package/dist/shard-ecko-scanner-registry.d.ts +38 -0
- package/dist/shard-ecko-scanner-registry.js +96 -0
- package/dist/shard-router.d.ts +23 -0
- package/dist/shard-router.js +69 -5
- package/dist/shard-supervisor.d.ts +11 -0
- package/dist/shard-supervisor.js +67 -4
- package/dist/shard-worker.d.ts +15 -1
- package/dist/shard-worker.js +95 -17
- package/dist/transient-violation-evidence.d.ts +39 -0
- package/dist/transient-violation-evidence.js +132 -0
- package/documentation/README.md +2 -0
- package/documentation/examples/.env.example +12 -0
- package/documentation/guides/self-hosting.md +17 -1
- package/documentation/modules/auto-moderation.md +51 -0
- package/documentation/modules/moderation.md +42 -0
- package/documentation/reference/environment.md +11 -0
- package/package.json +17 -5
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseBotEnvironment } from "@helyx/config";
|
|
2
2
|
import { StatusHeartbeatReporter } from "@helyx/status-client";
|
|
3
3
|
import { safeError, ServiceRegistry } from "@helyx/core";
|
|
4
|
-
import { checkDatabaseConnection, createDatabasePool, AuditRepository, GuildCommandRepository, HostedServerSafetyRepository, InteractionSessionRepository, PremiumAllocationRepository, RateLimitRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
|
|
4
|
+
import { checkDatabaseConnection, createDatabasePool, AuditRepository, GuildCommandRepository, HostedServerSafetyRepository, InteractionSessionRepository, EckoOperationsRepository, PremiumAllocationRepository, RateLimitRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
|
|
5
5
|
import { createRuntime } from "@helyx/framework";
|
|
6
6
|
import { createPlatformServices, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, PlatformMemberRoleGrantOwnershipService, PostgresMemberRoleGrantOwnershipStore, } from "@helyx/platform";
|
|
7
7
|
import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
@@ -19,9 +19,10 @@ import { loadConfiguredModulePackages, platformServiceOptions, } from "./modules
|
|
|
19
19
|
import { resolveModulePackageSpecifiers } from "./module-configuration.js";
|
|
20
20
|
import { createPermissionsModule } from "./permissions-module.js";
|
|
21
21
|
import { createManagementModule } from "./management-module.js";
|
|
22
|
-
import { DiscordMessageDeliveryService,
|
|
22
|
+
import { DiscordMessageDeliveryService, DiscordMemberRoleService, DiscordVerificationMemberService, DiscordResourceInspection, installModuleEvents, } from "./events.js";
|
|
23
|
+
import { DiscordModerationMemberEnforcementAdapter } from "./discord-member-enforcement-service.js";
|
|
23
24
|
import { DiscordModuleLoggingService } from "./module-logging.js";
|
|
24
|
-
import { BotControlService, createBotControlServer, DiscordGuildResourceDirectory, } from "./control-server.js";
|
|
25
|
+
import { BotControlService, BotControlTransportError, createBotControlServer, DiscordGuildResourceDirectory, } from "./control-server.js";
|
|
25
26
|
import { HttpVerificationService } from "./verification-service.js";
|
|
26
27
|
import { startShardSupervisor } from "./shard-supervisor.js";
|
|
27
28
|
import { parseShardWorkerLaunchContract, startShardWorker, } from "./shard-worker.js";
|
|
@@ -30,6 +31,9 @@ import { shardHealthSnapshotSchema, } from "./shard-contracts.js";
|
|
|
30
31
|
import { createSupportModule } from "./support-module.js";
|
|
31
32
|
import { createOAuthModule } from "./oauth-module.js";
|
|
32
33
|
import { createPrivacyModule } from "./privacy-module.js";
|
|
34
|
+
import { installModerationPlatformServices } from "./moderation-platform-services.js";
|
|
35
|
+
import { EckoEvidenceReceiver } from "./ecko-evidence-receiver.js";
|
|
36
|
+
import { EckoTransportAuthenticator } from "./ecko-transport-auth.js";
|
|
33
37
|
import { REVIEWED_PREMIUM_MODULE_IDS } from "@helyx/module-manifest";
|
|
34
38
|
import { createPremiumActivationModule } from "./premium-activation-module.js";
|
|
35
39
|
import { DiscordPremiumGuildAuthority } from "./premium-guild-authority.js";
|
|
@@ -120,8 +124,10 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
120
124
|
sslMode: environment.DATABASE_SSL,
|
|
121
125
|
applicationName: "helyx-bot",
|
|
122
126
|
});
|
|
127
|
+
const hostedOwnerObserver = new PostgresHostedGuildSafety(database);
|
|
128
|
+
const eckoOperations = new EckoOperationsRepository(database);
|
|
123
129
|
const hostedSafety = environment.HELYX_HOSTED_MODERATION_ENABLED
|
|
124
|
-
?
|
|
130
|
+
? hostedOwnerObserver
|
|
125
131
|
: undefined;
|
|
126
132
|
const developerModeration = environment.HELYX_HOSTED_MODERATION_ENABLED
|
|
127
133
|
? new HostedServerModerationClient(environment.HELYX_CLOUD_API_URL, environment.HELYX_BOT_CONTROL_SECRET)
|
|
@@ -226,7 +232,7 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
226
232
|
premiumCatalogueTimer.unref();
|
|
227
233
|
const client = new Client({
|
|
228
234
|
rest: discordRestOptions,
|
|
229
|
-
intents: gatewayIntentsForModules(featureModules, environment.HELYX_MESSAGE_CONTENT_ENABLED, environment.HELYX_GUILD_MEMBERS_ENABLED),
|
|
235
|
+
intents: gatewayIntentsForModules(featureModules, environment.HELYX_MESSAGE_CONTENT_ENABLED, environment.HELYX_GUILD_MEMBERS_ENABLED, environment.HELYX_RUNTIME_PROFILE),
|
|
230
236
|
partials: [Partials.Message, Partials.Channel, Partials.Reaction],
|
|
231
237
|
allowedMentions: { parse: [] },
|
|
232
238
|
});
|
|
@@ -390,11 +396,26 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
390
396
|
platformServices.set(HELYX_SERVICE_NAMES.resourceAuthorization, new CoreResourceAuthorizationService(platformServices.get(HELYX_SERVICE_NAMES.permissions)));
|
|
391
397
|
platformServices.set(HELYX_SERVICE_NAMES.discordMessages, new DiscordMessageDeliveryService(client));
|
|
392
398
|
platformServices.set(HELYX_SERVICE_NAMES.discordOwnedMessages, new DiscordOwnedMessageDeletionService(client));
|
|
393
|
-
platformServices.set(HELYX_SERVICE_NAMES.memberEnforcement, new
|
|
399
|
+
platformServices.set(HELYX_SERVICE_NAMES.memberEnforcement, new DiscordModerationMemberEnforcementAdapter(client));
|
|
394
400
|
const memberRoles = new DiscordMemberRoleService(client);
|
|
395
401
|
platformServices.set(HELYX_SERVICE_NAMES.memberRoles, memberRoles);
|
|
396
402
|
platformServices.set(HELYX_SERVICE_NAMES.memberRoleGrants, new PlatformMemberRoleGrantOwnershipService(new PostgresMemberRoleGrantOwnershipStore(database), memberRoles));
|
|
397
403
|
platformServices.set(HELYX_SERVICE_NAMES.discordResources, new DiscordResourceInspection(client));
|
|
404
|
+
const moderationPlatformServices = await installModerationPlatformServices({
|
|
405
|
+
services: platformServices,
|
|
406
|
+
serviceAccess,
|
|
407
|
+
client,
|
|
408
|
+
modules: publicModules,
|
|
409
|
+
runtimeProfile: environment.HELYX_RUNTIME_PROFILE,
|
|
410
|
+
deploymentEnvironment: environment.HELYX_DEPLOYMENT_ENVIRONMENT,
|
|
411
|
+
embedded: {
|
|
412
|
+
workers: environment.HELYX_AUTOMOD_EMBEDDED_WORKERS,
|
|
413
|
+
maximumItems: environment.HELYX_AUTOMOD_EMBEDDED_QUEUE_ITEMS,
|
|
414
|
+
maximumBytes: environment.HELYX_AUTOMOD_EMBEDDED_QUEUE_BYTES,
|
|
415
|
+
maximumItemsPerGuild: environment.HELYX_AUTOMOD_EMBEDDED_QUEUE_ITEMS_PER_GUILD,
|
|
416
|
+
refreshIntervalMs: environment.HELYX_AUTOMOD_EMBEDDED_REFRESH_SECONDS * 1_000,
|
|
417
|
+
},
|
|
418
|
+
});
|
|
398
419
|
platformServices.set(HELYX_SERVICE_NAMES.discordGuildHealth, new DiscordGuildHealthInspection(client, publicModules, serviceAccess));
|
|
399
420
|
platformServices.set(HELYX_SERVICE_NAMES.discordAnalytics, new DiscordAnalyticsInspection(client));
|
|
400
421
|
if (serviceOptions.inviteFingerprintKey)
|
|
@@ -416,10 +437,8 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
416
437
|
...(cloudManagementModule ? [cloudManagementModule.manifest.id] : []),
|
|
417
438
|
...(operationsModule ? [operationsModule.manifest.id] : []),
|
|
418
439
|
]);
|
|
419
|
-
const prepareGuilds = installGuildTracking(client, database, publicModules, bootstrapLogger, enabledCoreModuleIds, undefined, environment.HELYX_GUILD_RECONCILIATION_INTERVAL_SECONDS * 1_000, undefined, environment.DISCORD_CLIENT_ID, hostedSafety, hostedInviteGate);
|
|
420
|
-
const hostedLeaveWorker =
|
|
421
|
-
? new HostedServerLeaveWorker(database, client, bootstrapLogger)
|
|
422
|
-
: undefined;
|
|
440
|
+
const prepareGuilds = installGuildTracking(client, database, publicModules, bootstrapLogger, enabledCoreModuleIds, undefined, environment.HELYX_GUILD_RECONCILIATION_INTERVAL_SECONDS * 1_000, undefined, environment.DISCORD_CLIENT_ID, hostedSafety, hostedInviteGate, hostedOwnerObserver);
|
|
441
|
+
const hostedLeaveWorker = new HostedServerLeaveWorker(database, client, bootstrapLogger);
|
|
423
442
|
const commandsReady = installInteractions(client, modules, {
|
|
424
443
|
globalCommandMode: environment.HELYX_GLOBAL_COMMAND_MODE,
|
|
425
444
|
...(environment.DISCORD_INTERNAL_GUILD_ID
|
|
@@ -436,7 +455,10 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
436
455
|
...(operationsModule ? [operationsModule.manifest.id] : []),
|
|
437
456
|
]),
|
|
438
457
|
}, bootstrapLogger, serviceAccess);
|
|
439
|
-
const moduleEvents = installModuleEvents(client, publicModules, bootstrapLogger, serviceAccess
|
|
458
|
+
const moduleEvents = installModuleEvents(client, publicModules, bootstrapLogger, serviceAccess, {
|
|
459
|
+
runtimeProfile: environment.HELYX_RUNTIME_PROFILE,
|
|
460
|
+
evidenceStore: moderationPlatformServices.evidenceStore,
|
|
461
|
+
});
|
|
440
462
|
const controlService = new BotControlService(database, [
|
|
441
463
|
...publicModules,
|
|
442
464
|
gamingStatisticsRecordOwner,
|
|
@@ -498,6 +520,12 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
498
520
|
process.exitCode = 1;
|
|
499
521
|
bootstrapLogger.error({ event: "module_events.stop_failed", error: safeError(error) }, "Module event shutdown failed");
|
|
500
522
|
});
|
|
523
|
+
await moderationPlatformServices.embeddedScanner
|
|
524
|
+
?.stop()
|
|
525
|
+
.catch((error) => {
|
|
526
|
+
process.exitCode = 1;
|
|
527
|
+
bootstrapLogger.error({ event: "automod.embedded_stop_failed", error: safeError(error) }, "Embedded Auto Moderation scanner shutdown failed");
|
|
528
|
+
});
|
|
501
529
|
await runtime.stop().catch((error) => {
|
|
502
530
|
process.exitCode = 1;
|
|
503
531
|
bootstrapLogger.error({ event: "runtime.stop_failed", error: safeError(error) }, "Runtime shutdown failed");
|
|
@@ -529,6 +557,11 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
529
557
|
process.once("SIGTERM", onSigterm);
|
|
530
558
|
try {
|
|
531
559
|
await runtime.start();
|
|
560
|
+
await moderationPlatformServices.embeddedScanner
|
|
561
|
+
?.synchronize()
|
|
562
|
+
.catch((error) => {
|
|
563
|
+
bootstrapLogger.warn({ event: "automod.embedded_sync_failed", error: safeError(error) }, "Embedded Auto Moderation scanner started in Standard fallback");
|
|
564
|
+
});
|
|
532
565
|
await client.login(environment.DISCORD_TOKEN);
|
|
533
566
|
await commandsReady;
|
|
534
567
|
hostedLeaveWorker?.start();
|
|
@@ -584,11 +617,38 @@ async function startUnshardedBot(processEnvironment) {
|
|
|
584
617
|
}
|
|
585
618
|
await cloudConnector.start();
|
|
586
619
|
if (environment.HELYX_BOT_CONTROL_SECRET) {
|
|
620
|
+
const ecko = environment.HELYX_ECKO_TRANSPORT_SECRET
|
|
621
|
+
? {
|
|
622
|
+
authenticator: new EckoTransportAuthenticator(environment.HELYX_DEPLOYMENT_ENVIRONMENT, [
|
|
623
|
+
environment.HELYX_ECKO_TRANSPORT_SECRET,
|
|
624
|
+
...(environment.HELYX_ECKO_TRANSPORT_PREVIOUS_SECRET
|
|
625
|
+
? [environment.HELYX_ECKO_TRANSPORT_PREVIOUS_SECRET]
|
|
626
|
+
: []),
|
|
627
|
+
]),
|
|
628
|
+
registry: moderationPlatformServices.scannerRegistry,
|
|
629
|
+
evidence: new EckoEvidenceReceiver(moderationPlatformServices.evidenceStore, environment.HELYX_DEPLOYMENT_ENVIRONMENT, Buffer.from(environment.HELYX_ECKO_TRANSPORT_SECRET, "base64")),
|
|
630
|
+
gateState: () => eckoOperations.readGate(),
|
|
631
|
+
telemetry: {
|
|
632
|
+
accept: (batch) => eckoOperations.acceptTelemetry(batch),
|
|
633
|
+
},
|
|
634
|
+
}
|
|
635
|
+
: undefined;
|
|
587
636
|
controlServer = await createBotControlServer({
|
|
588
637
|
service: controlService,
|
|
589
638
|
secret: environment.HELYX_BOT_CONTROL_SECRET,
|
|
639
|
+
deliverHostedServerLeave: (input) => hostedLeaveWorker.deliverOperation(input),
|
|
640
|
+
refreshHostedServerOwner: async (guildId) => {
|
|
641
|
+
const guild = client.guilds.cache.get(guildId);
|
|
642
|
+
if (!guild)
|
|
643
|
+
throw new BotControlTransportError(404, "The hosted server is not installed");
|
|
644
|
+
return hostedOwnerObserver.refreshOwner(guild);
|
|
645
|
+
},
|
|
590
646
|
logger: { level: environment.LOG_LEVEL },
|
|
647
|
+
runtimeConfiguration: {
|
|
648
|
+
hostedModerationEnabled: environment.HELYX_HOSTED_MODERATION_ENABLED,
|
|
649
|
+
},
|
|
591
650
|
guildCommands: new GuildCommandReadService(database, environment.DISCORD_CLIENT_ID, () => guildCommandWorker?.getHealth(), environment.DISCORD_TOKEN, environment.HELYX_GUILD_COMMAND_WRITES_ENABLED, environment.HELYX_GUILD_COMMAND_WRITE_GUILD_IDS, environment.HELYX_GUILD_COMMAND_WRITE_SCOPE === "all", modules),
|
|
651
|
+
...(ecko ? { ecko } : {}),
|
|
592
652
|
});
|
|
593
653
|
await controlServer.listen({
|
|
594
654
|
host: "::",
|
|
@@ -667,7 +727,7 @@ function deploymentReference(processEnvironment) {
|
|
|
667
727
|
processEnvironment.GIT_COMMIT_SHA;
|
|
668
728
|
return value?.trim().slice(0, 12) || undefined;
|
|
669
729
|
}
|
|
670
|
-
export function gatewayIntentsForModules(modules, messageContentEnabled = false, guildMembersEnabled = false) {
|
|
730
|
+
export function gatewayIntentsForModules(modules, messageContentEnabled = false, guildMembersEnabled = false, runtimeProfile = "hosted") {
|
|
671
731
|
const intents = [GatewayIntentBits.Guilds];
|
|
672
732
|
if (modules.some((module) => module.events?.messageReactionAdd?.length ||
|
|
673
733
|
module.events?.messageReactionRemove?.length ||
|
|
@@ -675,8 +735,12 @@ export function gatewayIntentsForModules(modules, messageContentEnabled = false,
|
|
|
675
735
|
module.events?.messageReactionRemoveEmoji?.length)) {
|
|
676
736
|
intents.push(GatewayIntentBits.GuildMessageReactions);
|
|
677
737
|
}
|
|
678
|
-
const hasPrivilegedMessageCreate = modules.some((module) => module.events?.privilegedMessageCreate?.
|
|
679
|
-
|
|
738
|
+
const hasPrivilegedMessageCreate = modules.some((module) => module.events?.privilegedMessageCreate?.some(({ featureId }) => !featureId || runtimeProfile === "self-hosted") ||
|
|
739
|
+
module.events?.privilegedMessageUpdate?.some(({ featureId }) => !featureId || runtimeProfile === "self-hosted"));
|
|
740
|
+
const requiresMessageContent = modules.some((module) => module.requiredDiscordCapabilities?.includes("message-content") ||
|
|
741
|
+
(runtimeProfile === "self-hosted" &&
|
|
742
|
+
module.manifest.manifestVersion === "0.5" &&
|
|
743
|
+
module.manifest.features.some((feature) => feature.requiredDiscordCapabilities.includes("message-content"))));
|
|
680
744
|
if (((hasPrivilegedMessageCreate || requiresMessageContent) &&
|
|
681
745
|
messageContentEnabled) ||
|
|
682
746
|
modules.some((module) => module.events?.guildMessageCreate?.length ||
|
|
@@ -698,6 +762,12 @@ export function gatewayIntentsForModules(modules, messageContentEnabled = false,
|
|
|
698
762
|
if (modules.some((module) => module.events?.guildVoiceSession?.length)) {
|
|
699
763
|
intents.push(GatewayIntentBits.GuildVoiceStates);
|
|
700
764
|
}
|
|
765
|
+
if (modules.some((module) => module.events?.discordAutoModerationRuleCreate?.length ||
|
|
766
|
+
module.events?.discordAutoModerationRuleUpdate?.length ||
|
|
767
|
+
module.events?.discordAutoModerationRuleDelete?.length))
|
|
768
|
+
intents.push(GatewayIntentBits.AutoModerationConfiguration);
|
|
769
|
+
if (modules.some((module) => module.events?.discordAutoModerationActionExecution?.length))
|
|
770
|
+
intents.push(GatewayIntentBits.AutoModerationExecution);
|
|
701
771
|
if (modules.some((module) => module.events?.guildEmojiDelete?.length ||
|
|
702
772
|
module.events?.guildEmojiUpdate?.length)) {
|
|
703
773
|
intents.push(GatewayIntentBits.GuildExpressions);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ModerationActionRegistryService, ModerationActionResultV2, ModuleDefinition, ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
import type { DiscordViolationEvidenceDeliveryAdapter } from "./discord-violation-thread-service.js";
|
|
3
|
+
/** Local closed registry. In sharded mode it is instantiated in each guild owner. */
|
|
4
|
+
export declare class BotModerationActionRegistry implements ModerationActionRegistryService {
|
|
5
|
+
#private;
|
|
6
|
+
private readonly services;
|
|
7
|
+
private readonly evidenceDelivery?;
|
|
8
|
+
constructor(modules: readonly ModuleDefinition[], services: ServiceAccess, evidenceDelivery?: DiscordViolationEvidenceDeliveryAdapter | undefined);
|
|
9
|
+
execute(input: Parameters<ModerationActionRegistryService["execute"]>[0]): Promise<ModerationActionResultV2>;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=moderation-action-registry.d.ts.map
|
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { HELYX_AUTOMOD_ENHANCED_SCANNING_FEATURE } from "@helyx/module-manifest";
|
|
2
|
+
import { AUTOMOD_ENFORCEMENT_POLICY_CONTRACT_VERSION, AUTOMOD_ENFORCEMENT_POLICY_PROVIDER_ID, AUTOMOD_TIMEOUT_SECONDS, HELYX_SERVICE_NAMES, MODERATION_ACTION_CONTRACT_VERSION, MODERATION_ACTION_PROVIDER_ID, MODERATION_CONTRACT_LIMITS, } from "@helyx/sdk";
|
|
3
|
+
const MODERATION_MODULE_ID = "helyx.moderation";
|
|
4
|
+
const AUTOMOD_MODULE_ID = "helyx.automod";
|
|
5
|
+
const ALLOWED_CALLER_MODULE_IDS = new Set([AUTOMOD_MODULE_ID]);
|
|
6
|
+
const ID_PATTERN = /^\d{17,20}$/u;
|
|
7
|
+
const OPERATION_KEY_PATTERN = /^[A-Za-z0-9][A-Za-z0-9:._/-]{0,199}$/u;
|
|
8
|
+
const SAFE_CODE_PATTERN = /^[a-z0-9][a-z0-9._-]{0,99}$/u;
|
|
9
|
+
/** Local closed registry. In sharded mode it is instantiated in each guild owner. */
|
|
10
|
+
export class BotModerationActionRegistry {
|
|
11
|
+
services;
|
|
12
|
+
evidenceDelivery;
|
|
13
|
+
#actionProvider;
|
|
14
|
+
#policyProvider;
|
|
15
|
+
constructor(modules, services, evidenceDelivery) {
|
|
16
|
+
this.services = services;
|
|
17
|
+
this.evidenceDelivery = evidenceDelivery;
|
|
18
|
+
const actionProviders = modules.flatMap((module) => module.moderationActionProvider
|
|
19
|
+
? [
|
|
20
|
+
{
|
|
21
|
+
moduleId: module.manifest.id,
|
|
22
|
+
provider: module.moderationActionProvider,
|
|
23
|
+
},
|
|
24
|
+
]
|
|
25
|
+
: []);
|
|
26
|
+
if (actionProviders.length > 1)
|
|
27
|
+
throw new Error("Moderation action provider ownership is ambiguous");
|
|
28
|
+
const actionRegistration = actionProviders[0];
|
|
29
|
+
if (!actionRegistration) {
|
|
30
|
+
this.#actionProvider = null;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
if (actionRegistration.moduleId !== MODERATION_MODULE_ID ||
|
|
34
|
+
actionRegistration.provider.providerId !==
|
|
35
|
+
MODERATION_ACTION_PROVIDER_ID ||
|
|
36
|
+
actionRegistration.provider.contractVersion !==
|
|
37
|
+
MODERATION_ACTION_CONTRACT_VERSION ||
|
|
38
|
+
actionRegistration.provider.allowedCallerModuleIds.length < 1 ||
|
|
39
|
+
new Set(actionRegistration.provider.allowedCallerModuleIds).size !==
|
|
40
|
+
actionRegistration.provider.allowedCallerModuleIds.length ||
|
|
41
|
+
actionRegistration.provider.allowedCallerModuleIds.some((moduleId) => !ALLOWED_CALLER_MODULE_IDS.has(moduleId)))
|
|
42
|
+
throw new Error("Moderation action provider registration is invalid");
|
|
43
|
+
this.#actionProvider = actionRegistration.provider;
|
|
44
|
+
}
|
|
45
|
+
const policyProviders = modules.flatMap((module) => module.autoModerationEnforcementPolicyProvider
|
|
46
|
+
? [
|
|
47
|
+
{
|
|
48
|
+
moduleId: module.manifest.id,
|
|
49
|
+
provider: module.autoModerationEnforcementPolicyProvider,
|
|
50
|
+
},
|
|
51
|
+
]
|
|
52
|
+
: []);
|
|
53
|
+
if (policyProviders.length > 1)
|
|
54
|
+
throw new Error("Auto Moderation policy provider ownership is ambiguous");
|
|
55
|
+
const policyRegistration = policyProviders[0];
|
|
56
|
+
if (!policyRegistration) {
|
|
57
|
+
this.#policyProvider = null;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
if (policyRegistration.moduleId !== AUTOMOD_MODULE_ID ||
|
|
61
|
+
policyRegistration.provider.providerId !==
|
|
62
|
+
AUTOMOD_ENFORCEMENT_POLICY_PROVIDER_ID ||
|
|
63
|
+
policyRegistration.provider.contractVersion !==
|
|
64
|
+
AUTOMOD_ENFORCEMENT_POLICY_CONTRACT_VERSION)
|
|
65
|
+
throw new Error("Auto Moderation policy provider registration is invalid");
|
|
66
|
+
this.#policyProvider = policyRegistration.provider;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async execute(input) {
|
|
70
|
+
if (!this.#actionProvider ||
|
|
71
|
+
!this.#policyProvider ||
|
|
72
|
+
input.providerId !== MODERATION_ACTION_PROVIDER_ID ||
|
|
73
|
+
input.contractVersion !== MODERATION_ACTION_CONTRACT_VERSION)
|
|
74
|
+
return rejected("module_unavailable");
|
|
75
|
+
if (input.callerModuleId !== input.request.callerModuleId ||
|
|
76
|
+
!ALLOWED_CALLER_MODULE_IDS.has(input.callerModuleId) ||
|
|
77
|
+
!this.#actionProvider.allowedCallerModuleIds.includes(input.callerModuleId))
|
|
78
|
+
return rejected("caller_not_allowed");
|
|
79
|
+
if (!isValidRequest(input.request))
|
|
80
|
+
return rejected("invalid_request");
|
|
81
|
+
const installations = this.services.get(HELYX_SERVICE_NAMES.installations);
|
|
82
|
+
const availability = this.services.has(HELYX_SERVICE_NAMES.moduleAvailability)
|
|
83
|
+
? this.services.get(HELYX_SERVICE_NAMES.moduleAvailability)
|
|
84
|
+
: null;
|
|
85
|
+
try {
|
|
86
|
+
const modulesAvailable = (await installations.isModuleEnabled(input.request.guildId, MODERATION_MODULE_ID)) &&
|
|
87
|
+
(await installations.isModuleEnabled(input.request.guildId, input.callerModuleId)) &&
|
|
88
|
+
(!availability ||
|
|
89
|
+
((await availability.isAvailable(MODERATION_MODULE_ID)) &&
|
|
90
|
+
(await availability.isAvailable(input.callerModuleId))));
|
|
91
|
+
if (!modulesAvailable)
|
|
92
|
+
return rejected("module_unavailable");
|
|
93
|
+
if (input.request.detectionSource === "enhanced" &&
|
|
94
|
+
!(await enhancedFeatureAllowed(this.services, input.request.guildId)))
|
|
95
|
+
return rejected("feature_unavailable");
|
|
96
|
+
const policyContext = { services: this.services };
|
|
97
|
+
const resolution = await this.#policyProvider.resolve(policyContext, input.request);
|
|
98
|
+
if (resolution.outcome === "rejected")
|
|
99
|
+
return rejected(mapPolicyRejection(resolution.code));
|
|
100
|
+
if (!isValidPolicy(resolution))
|
|
101
|
+
return rejected("invalid_request");
|
|
102
|
+
if (resolution.configurationRevision !== input.request.configurationRevision)
|
|
103
|
+
return rejected("stale_revision");
|
|
104
|
+
const policy = copyPolicy(resolution);
|
|
105
|
+
const evidenceClaims = {
|
|
106
|
+
caseIds: new Set(),
|
|
107
|
+
threadIds: new Set(),
|
|
108
|
+
};
|
|
109
|
+
const requestServices = createRequestBoundServices(this.services, evidenceClaims);
|
|
110
|
+
return await this.#actionProvider.execute({
|
|
111
|
+
services: requestServices,
|
|
112
|
+
violationEvidence: {
|
|
113
|
+
deliver: async (delivery) => {
|
|
114
|
+
if (!input.request.evidenceReference ||
|
|
115
|
+
delivery.guildId !== input.request.guildId ||
|
|
116
|
+
!ID_PATTERN.test(delivery.threadId) ||
|
|
117
|
+
!OPERATION_KEY_PATTERN.test(delivery.caseId) ||
|
|
118
|
+
!OPERATION_KEY_PATTERN.test(delivery.operationKey) ||
|
|
119
|
+
!evidenceClaims.caseIds.has(delivery.caseId) ||
|
|
120
|
+
!evidenceClaims.threadIds.has(delivery.threadId))
|
|
121
|
+
return { outcome: "evidence_unavailable" };
|
|
122
|
+
if (!this.evidenceDelivery)
|
|
123
|
+
return { outcome: "evidence_unavailable" };
|
|
124
|
+
return await this.evidenceDelivery.deliverEvidence({
|
|
125
|
+
guildId: delivery.guildId,
|
|
126
|
+
threadId: delivery.threadId,
|
|
127
|
+
evidenceReference: input.request.evidenceReference,
|
|
128
|
+
operationKey: delivery.operationKey,
|
|
129
|
+
});
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
}, {
|
|
133
|
+
...input.request,
|
|
134
|
+
policy,
|
|
135
|
+
revalidatePolicy: async () => {
|
|
136
|
+
try {
|
|
137
|
+
const current = await this.#policyProvider?.resolve(policyContext, input.request);
|
|
138
|
+
return Boolean(current &&
|
|
139
|
+
current.outcome === "eligible" &&
|
|
140
|
+
isValidPolicy(current) &&
|
|
141
|
+
samePolicy(policy, current) &&
|
|
142
|
+
(input.request.detectionSource !== "enhanced" ||
|
|
143
|
+
(await enhancedFeatureAllowed(this.services, input.request.guildId))));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
return rejected("module_unavailable");
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function isValidRequest(request) {
|
|
157
|
+
return (OPERATION_KEY_PATTERN.test(request.operationKey) &&
|
|
158
|
+
(request.detectionSource === "native" ||
|
|
159
|
+
request.detectionSource === "enhanced") &&
|
|
160
|
+
ID_PATTERN.test(request.guildId) &&
|
|
161
|
+
ID_PATTERN.test(request.subjectUserId) &&
|
|
162
|
+
OPERATION_KEY_PATTERN.test(request.sourceEventId) &&
|
|
163
|
+
request.occurredAt instanceof Date &&
|
|
164
|
+
!Number.isNaN(request.occurredAt.getTime()) &&
|
|
165
|
+
OPERATION_KEY_PATTERN.test(request.ruleId) &&
|
|
166
|
+
request.configurationRevision.length > 0 &&
|
|
167
|
+
request.configurationRevision.length <= 200 &&
|
|
168
|
+
SAFE_CODE_PATTERN.test(request.reasonCode) &&
|
|
169
|
+
["low", "medium", "high", "critical"].includes(request.severity) &&
|
|
170
|
+
["exact", "native_equivalent", "high", "medium", "low"].includes(request.confidenceClass) &&
|
|
171
|
+
(request.sourceMessage === null ||
|
|
172
|
+
(ID_PATTERN.test(request.sourceMessage.channelId) &&
|
|
173
|
+
(request.sourceMessage.messageId === null ||
|
|
174
|
+
ID_PATTERN.test(request.sourceMessage.messageId)))) &&
|
|
175
|
+
isValidEvidenceReference(request) &&
|
|
176
|
+
((request.requestKind === "detection" &&
|
|
177
|
+
request.observedAction === undefined) ||
|
|
178
|
+
(request.requestKind === "observation" &&
|
|
179
|
+
isValidObservedAction(request.observedAction))));
|
|
180
|
+
}
|
|
181
|
+
function isValidObservedAction(action) {
|
|
182
|
+
if (!action || typeof action !== "object" || !("type" in action))
|
|
183
|
+
return false;
|
|
184
|
+
return (action.type === "discord_native_block_message" ||
|
|
185
|
+
(action.type === "discord_native_timeout" &&
|
|
186
|
+
AUTOMOD_TIMEOUT_SECONDS.includes(action.durationSeconds)));
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* The Moderation provider gets request-scoped views of the two services that
|
|
190
|
+
* establish the case and thread. Evidence delivery is then limited to IDs
|
|
191
|
+
* returned by those services during this exact action invocation.
|
|
192
|
+
*/
|
|
193
|
+
function createRequestBoundServices(services, claims) {
|
|
194
|
+
return {
|
|
195
|
+
has: (name) => services.has(name),
|
|
196
|
+
get: (name) => {
|
|
197
|
+
if (name === HELYX_SERVICE_NAMES.atomicModerationCases) {
|
|
198
|
+
const service = services.get(name);
|
|
199
|
+
const bound = {
|
|
200
|
+
createPendingCase: async (input) => {
|
|
201
|
+
const result = await service.createPendingCase(input);
|
|
202
|
+
if (result.outcome === "created" || result.outcome === "replayed")
|
|
203
|
+
claims.caseIds.add(result.caseId);
|
|
204
|
+
return result;
|
|
205
|
+
},
|
|
206
|
+
createConfirmedAutoModerationOffenceCase: async (input) => {
|
|
207
|
+
const result = await service.createConfirmedAutoModerationOffenceCase(input);
|
|
208
|
+
if (result.outcome === "created" || result.outcome === "replayed")
|
|
209
|
+
claims.caseIds.add(result.caseId);
|
|
210
|
+
return result;
|
|
211
|
+
},
|
|
212
|
+
createAdditionalPendingAttempt: (input) => service.createAdditionalPendingAttempt(input),
|
|
213
|
+
createCorrectionCase: (input) => service.createCorrectionCase(input),
|
|
214
|
+
settleActionAttempt: (input) => service.settleActionAttempt(input),
|
|
215
|
+
recordObservedAction: (input) => service.recordObservedAction(input),
|
|
216
|
+
listReconciliationCandidates: (input) => service.listReconciliationCandidates(input),
|
|
217
|
+
disassociateCase: (input) => service.disassociateCase(input),
|
|
218
|
+
executeRetention: (input) => service.executeRetention(input),
|
|
219
|
+
};
|
|
220
|
+
return bound;
|
|
221
|
+
}
|
|
222
|
+
if (name === HELYX_SERVICE_NAMES.violationThreads) {
|
|
223
|
+
const service = services.get(name);
|
|
224
|
+
const bound = {
|
|
225
|
+
inspectConfiguration: (input) => service.inspectConfiguration(input),
|
|
226
|
+
createAccessRole: (input) => service.createAccessRole(input),
|
|
227
|
+
configureParent: (input) => service.configureParent(input),
|
|
228
|
+
create: async (input) => {
|
|
229
|
+
const result = await service.create(input);
|
|
230
|
+
if (result.outcome === "created" ||
|
|
231
|
+
result.outcome === "already_exists")
|
|
232
|
+
claims.threadIds.add(result.threadId);
|
|
233
|
+
return result;
|
|
234
|
+
},
|
|
235
|
+
addMember: (input) => service.addMember(input),
|
|
236
|
+
removeMember: (input) => service.removeMember(input),
|
|
237
|
+
send: (input) => service.send(input),
|
|
238
|
+
close: (input) => service.close(input),
|
|
239
|
+
reconcile: (input) => service.reconcile(input),
|
|
240
|
+
};
|
|
241
|
+
return bound;
|
|
242
|
+
}
|
|
243
|
+
return services.get(name);
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
function isValidPolicy(policy) {
|
|
248
|
+
return (policy.policyFingerprint.length > 0 &&
|
|
249
|
+
policy.policyFingerprint.length <= 200 &&
|
|
250
|
+
Number.isSafeInteger(policy.ruleRevision) &&
|
|
251
|
+
policy.ruleRevision > 0 &&
|
|
252
|
+
policy.configurationRevision.length > 0 &&
|
|
253
|
+
policy.configurationRevision.length <= 200 &&
|
|
254
|
+
policy.windowStartsAt instanceof Date &&
|
|
255
|
+
!Number.isNaN(policy.windowStartsAt.getTime()) &&
|
|
256
|
+
typeof policy.openViolationThread === "boolean" &&
|
|
257
|
+
validRoleIds(policy.globalIgnoredRoleIds, MODERATION_CONTRACT_LIMITS.ignoredRolesPerScope) &&
|
|
258
|
+
validRoleIds(policy.ruleIgnoredRoleIds, MODERATION_CONTRACT_LIMITS.ignoredRolesPerScope) &&
|
|
259
|
+
Boolean(policy.actionPolicy) &&
|
|
260
|
+
isValidAction(policy.actionPolicy.first) &&
|
|
261
|
+
isValidAction(policy.actionPolicy.second) &&
|
|
262
|
+
isValidAction(policy.actionPolicy.thirdAndLater));
|
|
263
|
+
}
|
|
264
|
+
function isValidAction(action) {
|
|
265
|
+
if (!action || typeof action !== "object" || !("type" in action))
|
|
266
|
+
return false;
|
|
267
|
+
switch (action.type) {
|
|
268
|
+
case "case_only":
|
|
269
|
+
case "delete_message":
|
|
270
|
+
case "warn":
|
|
271
|
+
case "kick":
|
|
272
|
+
return true;
|
|
273
|
+
case "timeout":
|
|
274
|
+
return AUTOMOD_TIMEOUT_SECONDS.includes(action.durationSeconds);
|
|
275
|
+
case "ban":
|
|
276
|
+
return [0, 3_600, 21_600, 86_400, 259_200, 604_800].includes(action.deleteMessageSeconds);
|
|
277
|
+
case "demote":
|
|
278
|
+
return (action.roleIds.length > 0 &&
|
|
279
|
+
validRoleIds(action.roleIds, MODERATION_CONTRACT_LIMITS.automaticDemotionRoles));
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function isValidEvidenceReference(request) {
|
|
283
|
+
const reference = request.evidenceReference;
|
|
284
|
+
if (!reference)
|
|
285
|
+
return true;
|
|
286
|
+
if (reference.key.trim().length < 1 ||
|
|
287
|
+
reference.key.length > 160 ||
|
|
288
|
+
reference.sourceEventId !== request.sourceEventId ||
|
|
289
|
+
reference.guildId !== request.guildId ||
|
|
290
|
+
reference.authorUserId !== request.subjectUserId ||
|
|
291
|
+
request.sourceMessage === null ||
|
|
292
|
+
reference.channelId !== request.sourceMessage.channelId ||
|
|
293
|
+
reference.messageId !== request.sourceMessage.messageId)
|
|
294
|
+
return false;
|
|
295
|
+
return (reference.source === "discord_auto_moderation" ||
|
|
296
|
+
(reference.source === "enhanced_scanner" &&
|
|
297
|
+
reference.configurationRevision === request.configurationRevision));
|
|
298
|
+
}
|
|
299
|
+
function validRoleIds(roleIds, maximum) {
|
|
300
|
+
return (roleIds.length <= maximum &&
|
|
301
|
+
new Set(roleIds).size === roleIds.length &&
|
|
302
|
+
roleIds.every((roleId) => ID_PATTERN.test(roleId)));
|
|
303
|
+
}
|
|
304
|
+
function copyPolicy(policy) {
|
|
305
|
+
return {
|
|
306
|
+
...policy,
|
|
307
|
+
windowStartsAt: new Date(policy.windowStartsAt),
|
|
308
|
+
globalIgnoredRoleIds: Object.freeze([...policy.globalIgnoredRoleIds]),
|
|
309
|
+
ruleIgnoredRoleIds: Object.freeze([...policy.ruleIgnoredRoleIds]),
|
|
310
|
+
actionPolicy: Object.freeze({
|
|
311
|
+
first: copyAction(policy.actionPolicy.first),
|
|
312
|
+
second: copyAction(policy.actionPolicy.second),
|
|
313
|
+
thirdAndLater: copyAction(policy.actionPolicy.thirdAndLater),
|
|
314
|
+
}),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function copyAction(action) {
|
|
318
|
+
return action.type === "demote"
|
|
319
|
+
? { ...action, roleIds: Object.freeze([...action.roleIds]) }
|
|
320
|
+
: { ...action };
|
|
321
|
+
}
|
|
322
|
+
function samePolicy(expected, current) {
|
|
323
|
+
return (expected.policyFingerprint === current.policyFingerprint &&
|
|
324
|
+
expected.ruleRevision === current.ruleRevision &&
|
|
325
|
+
expected.configurationRevision === current.configurationRevision &&
|
|
326
|
+
expected.windowStartsAt.getTime() === current.windowStartsAt.getTime() &&
|
|
327
|
+
expected.openViolationThread === current.openViolationThread &&
|
|
328
|
+
sameStrings(expected.globalIgnoredRoleIds, current.globalIgnoredRoleIds) &&
|
|
329
|
+
sameStrings(expected.ruleIgnoredRoleIds, current.ruleIgnoredRoleIds) &&
|
|
330
|
+
sameAction(expected.actionPolicy.first, current.actionPolicy.first) &&
|
|
331
|
+
sameAction(expected.actionPolicy.second, current.actionPolicy.second) &&
|
|
332
|
+
sameAction(expected.actionPolicy.thirdAndLater, current.actionPolicy.thirdAndLater));
|
|
333
|
+
}
|
|
334
|
+
function sameAction(expected, current) {
|
|
335
|
+
if (expected.type !== current.type)
|
|
336
|
+
return false;
|
|
337
|
+
if (expected.type === "timeout" && current.type === "timeout")
|
|
338
|
+
return expected.durationSeconds === current.durationSeconds;
|
|
339
|
+
if (expected.type === "ban" && current.type === "ban")
|
|
340
|
+
return expected.deleteMessageSeconds === current.deleteMessageSeconds;
|
|
341
|
+
if (expected.type === "demote" && current.type === "demote")
|
|
342
|
+
return sameStrings(expected.roleIds, current.roleIds);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
function sameStrings(expected, current) {
|
|
346
|
+
return (expected.length === current.length &&
|
|
347
|
+
expected.every((value, index) => value === current[index]));
|
|
348
|
+
}
|
|
349
|
+
function mapPolicyRejection(code) {
|
|
350
|
+
switch (code) {
|
|
351
|
+
case "source_unresolved":
|
|
352
|
+
return "invalid_request";
|
|
353
|
+
case "source_exempt":
|
|
354
|
+
return "subject_exempt";
|
|
355
|
+
default:
|
|
356
|
+
return code;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async function enhancedFeatureAllowed(services, guildId) {
|
|
360
|
+
if (!services.has(HELYX_SERVICE_NAMES.featureAccess))
|
|
361
|
+
return false;
|
|
362
|
+
const access = await services
|
|
363
|
+
.get(HELYX_SERVICE_NAMES.featureAccess)
|
|
364
|
+
.check({
|
|
365
|
+
guildId,
|
|
366
|
+
moduleId: AUTOMOD_MODULE_ID,
|
|
367
|
+
featureId: HELYX_AUTOMOD_ENHANCED_SCANNING_FEATURE,
|
|
368
|
+
})
|
|
369
|
+
.catch(() => ({ decision: "dependency_unhealthy" }));
|
|
370
|
+
return access.decision === "granted";
|
|
371
|
+
}
|
|
372
|
+
function rejected(code) {
|
|
373
|
+
return { outcome: "rejected", code };
|
|
374
|
+
}
|
|
375
|
+
//# sourceMappingURL=moderation-action-registry.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AtomicModerationCaseService, type ModuleDefinition, type ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
/** Adds Auto Moderation unlinking after the authoritative case transition. */
|
|
3
|
+
export declare function decorateModerationCaseDisassociation(input: {
|
|
4
|
+
modules: readonly ModuleDefinition[];
|
|
5
|
+
services: ServiceAccess;
|
|
6
|
+
service: AtomicModerationCaseService;
|
|
7
|
+
}): AtomicModerationCaseService;
|
|
8
|
+
export declare function installModerationCaseDisassociation(input: {
|
|
9
|
+
modules: readonly ModuleDefinition[];
|
|
10
|
+
services: Map<string, unknown>;
|
|
11
|
+
serviceAccess: ServiceAccess;
|
|
12
|
+
}): void;
|
|
13
|
+
//# sourceMappingURL=moderation-case-disassociation.d.ts.map
|