@helyx/bot 1.4.0 → 1.5.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/channel-audit-configuration.d.ts +7 -0
  3. package/dist/channel-audit-configuration.js +40 -0
  4. package/dist/channel-audit-control.d.ts +14 -0
  5. package/dist/channel-audit-control.js +61 -0
  6. package/dist/channel-audit-permissions.d.ts +56 -0
  7. package/dist/channel-audit-permissions.js +417 -0
  8. package/dist/channel-audit-routes.d.ts +4 -0
  9. package/dist/channel-audit-routes.js +42 -0
  10. package/dist/cloud-connector.js +2 -0
  11. package/dist/cloud-control-dispatcher.js +22 -0
  12. package/dist/command-registration-policy.js +1 -0
  13. package/dist/control-errors.d.ts +8 -0
  14. package/dist/control-errors.js +24 -0
  15. package/dist/control-server-schemas.d.ts +2 -2
  16. package/dist/control-server.d.ts +4 -1
  17. package/dist/control-server.js +18 -25
  18. package/dist/diagnostic-command.d.ts +76 -0
  19. package/dist/diagnostic-command.js +1962 -0
  20. package/dist/discord-channel-audit.d.ts +9 -0
  21. package/dist/discord-channel-audit.js +195 -0
  22. package/dist/discord-guild-health.d.ts +2 -2
  23. package/dist/discord-role-safety.d.ts +4 -0
  24. package/dist/discord-role-safety.js +30 -0
  25. package/dist/events.d.ts +2 -0
  26. package/dist/events.js +5 -20
  27. package/dist/hosted-server-moderation.d.ts +2 -2
  28. package/dist/index.js +30 -1
  29. package/dist/shard-contracts.d.ts +87 -0
  30. package/dist/shard-contracts.js +39 -0
  31. package/dist/shard-router.d.ts +2 -0
  32. package/dist/shard-router.js +12 -0
  33. package/dist/shard-supervisor.js +7 -0
  34. package/dist/shard-worker.d.ts +1 -0
  35. package/dist/shard-worker.js +38 -1
  36. package/documentation/reference/environment.md +1 -1
  37. package/package.json +7 -7
@@ -0,0 +1,9 @@
1
+ import type { Client } from "discord.js";
2
+ import type { ChannelAuditInput, ChannelAuditModuleId, ChannelAuditReport } from "@helyx/sdk";
3
+ /** Fresh guild roles/channels and the bot member only. Never reads messages or enumerates members. */
4
+ export declare function inspectDiscordChannelAudit(client: Client, input: {
5
+ guildId: string;
6
+ moduleId: ChannelAuditModuleId;
7
+ assumptions: ChannelAuditInput;
8
+ }): Promise<ChannelAuditReport>;
9
+ //# sourceMappingURL=discord-channel-audit.d.ts.map
@@ -0,0 +1,195 @@
1
+ import { PermissionFlagsBits } from "discord.js";
2
+ import { channelAuditInputSchema, channelAuditReportSchema, } from "@helyx/control-protocol/channel-audit";
3
+ import { buildChannelAuditReport, } from "./channel-audit-permissions.js";
4
+ import { dangerousRolePermissions } from "./discord-role-safety.js";
5
+ async function readBefore(read, deadline) {
6
+ const remaining = deadline - Date.now();
7
+ if (remaining <= 0)
8
+ throw new Error("Channel audit read deadline reached");
9
+ let timer;
10
+ try {
11
+ return await Promise.race([
12
+ read(),
13
+ new Promise((_, reject) => {
14
+ timer = setTimeout(() => reject(new Error("Channel audit read deadline reached")), remaining);
15
+ timer.unref();
16
+ }),
17
+ ]);
18
+ }
19
+ finally {
20
+ if (timer)
21
+ clearTimeout(timer);
22
+ }
23
+ }
24
+ /** Fresh guild roles/channels and the bot member only. Never reads messages or enumerates members. */
25
+ export async function inspectDiscordChannelAudit(client, input) {
26
+ const assumptions = channelAuditInputSchema.parse(input.assumptions);
27
+ const deadline = Date.now() + 8_500;
28
+ const snapshot = {
29
+ guildId: input.guildId,
30
+ roles: [],
31
+ rolesComplete: false,
32
+ channels: [],
33
+ fetchedChannels: 0,
34
+ totalChannelsKnown: false,
35
+ fetchLimitations: [],
36
+ };
37
+ const guild = await readBefore(() => client.guilds.fetch(input.guildId), deadline).catch(() => null);
38
+ if (!guild) {
39
+ snapshot.fetchLimitations.push("The current Discord server could not be fetched; channel and role coverage is incomplete.");
40
+ return channelAuditReportSchema.parse(buildChannelAuditReport(snapshot, input.moduleId, assumptions, new Date().toISOString()));
41
+ }
42
+ if (guild.id !== input.guildId)
43
+ throw new Error("Discord returned a different server for this inspection");
44
+ const [rolesResult, channelsResult, memberResult] = await Promise.allSettled([
45
+ readBefore(() => guild.roles.fetch(), deadline),
46
+ readBefore(() => guild.channels.fetch(), deadline),
47
+ readBefore(() => guild.members.fetchMe({ force: true }), deadline),
48
+ ]);
49
+ const me = memberResult.status === "fulfilled" ? memberResult.value : null;
50
+ if (me && me.guild.id !== guild.id)
51
+ throw new Error("Discord bot member belongs to a different server");
52
+ if (!me)
53
+ snapshot.fetchLimitations.push("Helyx's current server member could not be fetched; bot publishing and assignment checks may be unknown.");
54
+ if (rolesResult.status === "fulfilled") {
55
+ snapshot.rolesComplete = true;
56
+ const canManage = me?.permissions.has(PermissionFlagsBits.ManageRoles) ?? false;
57
+ snapshot.roles = [...rolesResult.value.values()].map((role) => {
58
+ if (role.guild.id !== guild.id)
59
+ throw new Error("Discord role belongs to a different server");
60
+ const reason = role.id === guild.id
61
+ ? "The everyone role cannot be assigned."
62
+ : role.managed
63
+ ? "This role is managed by Discord or an integration and cannot be assigned by Helyx."
64
+ : !me
65
+ ? undefined
66
+ : !canManage
67
+ ? "Helyx lacks Manage Roles in this server."
68
+ : role.comparePositionTo(me.roles.highest) >= 0
69
+ ? "The selected role is at or above Helyx's highest role."
70
+ : undefined;
71
+ const dangerous = dangerousRolePermissions(role.permissions.bitfield);
72
+ return {
73
+ id: role.id,
74
+ permissions: role.permissions.bitfield,
75
+ managed: role.managed,
76
+ assignable: reason ? false : me ? true : null,
77
+ ...(reason ? { assignmentReason: reason } : {}),
78
+ ...(dangerous.length
79
+ ? {
80
+ assignmentPolicyReason: `Helyx's role-safety policy blocks managing roles with ${dangerous.join(", ")}. Discord hierarchy alone does not make this role eligible.`,
81
+ }
82
+ : {}),
83
+ };
84
+ });
85
+ }
86
+ else
87
+ snapshot.fetchLimitations.push("The current Discord role list could not be fetched; missing roles are unknown, not confirmed deleted.");
88
+ const observedIds = new Set();
89
+ if (channelsResult.status === "fulfilled") {
90
+ snapshot.fetchedChannels = channelsResult.value.size;
91
+ for (const channel of channelsResult.value.values()) {
92
+ if (!channel)
93
+ continue;
94
+ if (channel.guild.id !== guild.id)
95
+ throw new Error("Discord channel belongs to a different server");
96
+ observedIds.add(channel.id);
97
+ const flags = "flags" in channel ? BigInt(channel.flags.bitfield) : 0n;
98
+ const obfuscated = (flags & 131072n) !== 0n || channel.name === "___hidden___";
99
+ const overwrites = "permissionOverwrites" in channel
100
+ ? [...channel.permissionOverwrites.cache.values()].map((overwrite) => ({
101
+ id: overwrite.id,
102
+ type: overwrite.type,
103
+ allow: overwrite.allow.bitfield,
104
+ deny: overwrite.deny.bitfield,
105
+ }))
106
+ : [];
107
+ const botPermissions = !obfuscated && me
108
+ ? (channel.permissionsFor(me)?.bitfield ?? null)
109
+ : null;
110
+ snapshot.channels.push({
111
+ id: channel.id,
112
+ name: obfuscated ? "Unknown channel" : channel.name,
113
+ type: channel.type,
114
+ obfuscated,
115
+ overwrites: obfuscated ? [] : overwrites,
116
+ botPermissions,
117
+ });
118
+ }
119
+ snapshot.totalChannelsKnown =
120
+ me?.permissions.has(PermissionFlagsBits.Administrator) ?? false;
121
+ }
122
+ else
123
+ snapshot.fetchLimitations.push("Discord's channel list could not be fetched; cached metadata is not used as fresh permission evidence.");
124
+ // Bulk lists can omit invisible channels. Resolve only explicitly selected
125
+ // missing IDs; a permission failure or deadline remains unknown.
126
+ const selected = new Set([
127
+ assumptions.entryChannelId,
128
+ ...assumptions.allowedChannelIds,
129
+ ...(assumptions.reviewChannelId ? [assumptions.reviewChannelId] : []),
130
+ ]);
131
+ snapshot.invalidSelectedChannels = [];
132
+ for (const id of selected) {
133
+ if (observedIds.has(id))
134
+ continue;
135
+ try {
136
+ const channel = await readBefore(() => guild.channels.fetch(id, { force: true, cache: false }), deadline);
137
+ if (!channel)
138
+ continue;
139
+ if (channel.guild.id !== guild.id) {
140
+ snapshot.invalidSelectedChannels.push({ id, reason: "wrong_guild" });
141
+ continue;
142
+ }
143
+ observedIds.add(id);
144
+ const obfuscated = (BigInt("flags" in channel ? channel.flags.bitfield : 0) & 131072n) !==
145
+ 0n || channel.name === "___hidden___";
146
+ snapshot.channels.push({
147
+ id,
148
+ name: obfuscated ? "Unknown channel" : channel.name,
149
+ type: channel.type,
150
+ obfuscated,
151
+ overwrites: !obfuscated && "permissionOverwrites" in channel
152
+ ? [...channel.permissionOverwrites.cache.values()].map((overwrite) => ({
153
+ id: overwrite.id,
154
+ type: overwrite.type,
155
+ allow: overwrite.allow.bitfield,
156
+ deny: overwrite.deny.bitfield,
157
+ }))
158
+ : [],
159
+ botPermissions: !obfuscated && me
160
+ ? (channel.permissionsFor(me)?.bitfield ?? null)
161
+ : null,
162
+ });
163
+ snapshot.fetchedChannels += 1;
164
+ }
165
+ catch (error) {
166
+ const code = error && typeof error === "object" && "code" in error
167
+ ? error.code
168
+ : undefined;
169
+ if (code === 10003 || code === "GuildChannelUnowned")
170
+ snapshot.invalidSelectedChannels.push({
171
+ id,
172
+ reason: code === 10003 ? "not_found" : "wrong_guild",
173
+ });
174
+ }
175
+ }
176
+ // Cache-only entries establish known inventory, never current permissions or deletion.
177
+ for (const channel of guild.channels.cache.values()) {
178
+ if (observedIds.has(channel.id))
179
+ continue;
180
+ snapshot.channels.push(unknownChannel(channel));
181
+ snapshot.totalChannelsKnown = false;
182
+ }
183
+ return channelAuditReportSchema.parse(buildChannelAuditReport(snapshot, input.moduleId, assumptions, new Date().toISOString()));
184
+ }
185
+ function unknownChannel(channel) {
186
+ return {
187
+ id: channel.id,
188
+ name: "Unknown channel",
189
+ type: channel.type,
190
+ obfuscated: true,
191
+ overwrites: [],
192
+ botPermissions: null,
193
+ };
194
+ }
195
+ //# sourceMappingURL=discord-channel-audit.js.map
@@ -163,8 +163,8 @@ export declare class DiscordGuildHealthInspection implements DiscordGuildHealthI
163
163
  resourceValidationValid: boolean | null;
164
164
  activationValid: boolean | null;
165
165
  catalogueMatches: boolean;
166
- requiredCapabilities: readonly ("guild-members" | "message-content")[];
167
- missingCapabilities: ("guild-members" | "message-content")[];
166
+ requiredCapabilities: readonly ("message-content" | "guild-members")[];
167
+ missingCapabilities: ("message-content" | "guild-members")[];
168
168
  missingPermissions: string[];
169
169
  healthStatus: "healthy" | "degraded" | "unhealthy";
170
170
  healthMessage: string | null;
@@ -0,0 +1,4 @@
1
+ /** The existing Helyx role-grant policy, shared by delivery and inspection. */
2
+ export declare const dangerousRolePermissionNames: Set<string>;
3
+ export declare function dangerousRolePermissions(permissions: bigint): string[];
4
+ //# sourceMappingURL=discord-role-safety.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { PermissionsBitField } from "discord.js";
2
+ /** The existing Helyx role-grant policy, shared by delivery and inspection. */
3
+ export const dangerousRolePermissionNames = new Set([
4
+ "ADMINISTRATOR",
5
+ "BAN_MEMBERS",
6
+ "DEAFEN_MEMBERS",
7
+ "KICK_MEMBERS",
8
+ "MANAGE_CHANNELS",
9
+ "MANAGE_EVENTS",
10
+ "MANAGE_GUILD",
11
+ "MANAGE_GUILD_EXPRESSIONS",
12
+ "MANAGE_MESSAGES",
13
+ "MANAGE_NICKNAMES",
14
+ "MANAGE_ROLES",
15
+ "MANAGE_THREADS",
16
+ "MANAGE_WEBHOOKS",
17
+ "MENTION_EVERYONE",
18
+ "MODERATE_MEMBERS",
19
+ "MOVE_MEMBERS",
20
+ "MUTE_MEMBERS",
21
+ "VIEW_AUDIT_LOG",
22
+ ]);
23
+ export function dangerousRolePermissions(permissions) {
24
+ return new PermissionsBitField(permissions)
25
+ .toArray()
26
+ .map((permission) => permission.replaceAll(/([a-z0-9])([A-Z])/g, "$1_$2").toUpperCase())
27
+ .filter((permission) => dangerousRolePermissionNames.has(permission))
28
+ .sort();
29
+ }
30
+ //# sourceMappingURL=discord-role-safety.js.map
package/dist/events.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { DiscordChannelAudienceInspection, DiscordChannelInspection, DiscordEmojiInspection, DiscordMessageInspection, DiscordGuildCapabilityInspection, DiscordGuildChannelAudienceInspection, DiscordResourceInspectionService, DiscordRoleInspection, DiscordMessageService, InstallationService, MemberEnforcementService, MemberRoleService, ModuleDefinition, ServiceAccess, VerificationMemberService } from "@helyx/sdk";
2
+ import { inspectDiscordChannelAudit } from "./discord-channel-audit.js";
2
3
  import { type Client, type MessageReaction, type PartialMessageReaction } from "discord.js";
3
4
  import { type InteractionLogger } from "./interactions.js";
4
5
  import type { TransientViolationEvidenceStore } from "./transient-violation-evidence.js";
@@ -80,6 +81,7 @@ export declare class DiscordResourceInspection implements DiscordResourceInspect
80
81
  #private;
81
82
  private readonly client;
82
83
  constructor(client: Client);
84
+ inspectChannelAudit(input: Parameters<typeof inspectDiscordChannelAudit>[1]): Promise<import("@helyx/sdk").ChannelAuditReport>;
83
85
  inspectGuildCapabilities(input: {
84
86
  guildId: string;
85
87
  }): Promise<DiscordGuildCapabilityInspection>;
package/dist/events.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { HELYX_SERVICE_NAMES } from "@helyx/sdk";
2
+ import { inspectDiscordChannelAudit } from "./discord-channel-audit.js";
3
+ import { dangerousRolePermissionNames } from "./discord-role-safety.js";
2
4
  import { safeError } from "@helyx/core";
3
5
  import { ChannelType, Events, GatewayIntentBits, GuildMemberFlags, PermissionFlagsBits, RESTJSONErrorCodes, UserFlags, } from "discord.js";
4
6
  import { toReplyOptions } from "./interactions.js";
@@ -1272,26 +1274,6 @@ export function reactionCacheKey(emojiIdentifier) {
1272
1274
  const discordSnowflake = /^\d{17,20}$/u;
1273
1275
  const moduleIdentifier = /^helyx\.[a-z0-9][a-z0-9-]*$/u;
1274
1276
  const customEmojiIdentifier = /^(?:<a?:([A-Za-z0-9_]{2,32}):(\d{17,20})>|([A-Za-z0-9_]{2,32}):(\d{17,20}))$/u;
1275
- const dangerousRolePermissionNames = new Set([
1276
- "ADMINISTRATOR",
1277
- "BAN_MEMBERS",
1278
- "DEAFEN_MEMBERS",
1279
- "KICK_MEMBERS",
1280
- "MANAGE_CHANNELS",
1281
- "MANAGE_EVENTS",
1282
- "MANAGE_GUILD",
1283
- "MANAGE_GUILD_EXPRESSIONS",
1284
- "MANAGE_MESSAGES",
1285
- "MANAGE_NICKNAMES",
1286
- "MANAGE_ROLES",
1287
- "MANAGE_THREADS",
1288
- "MANAGE_WEBHOOKS",
1289
- "MENTION_EVERYONE",
1290
- "MODERATE_MEMBERS",
1291
- "MOVE_MEMBERS",
1292
- "MUTE_MEMBERS",
1293
- "VIEW_AUDIT_LOG",
1294
- ]);
1295
1277
  const directMessageAttemptTimeoutMs = 5_000;
1296
1278
  const guildChannelAudienceInspectionLimit = 500;
1297
1279
  export class DiscordMessageDeliveryService {
@@ -1696,6 +1678,9 @@ export class DiscordResourceInspection {
1696
1678
  constructor(client) {
1697
1679
  this.client = client;
1698
1680
  }
1681
+ inspectChannelAudit(input) {
1682
+ return inspectDiscordChannelAudit(this.client, input);
1683
+ }
1699
1684
  async inspectGuildCapabilities(input) {
1700
1685
  validateSnowflake(input.guildId, "guild");
1701
1686
  const guild = await this.client.guilds.fetch(input.guildId);
@@ -11,8 +11,8 @@ declare const resultSchema: z.ZodObject<{
11
11
  }>;
12
12
  outcome: z.ZodEnum<{
13
13
  completed: "completed";
14
- failed: "failed";
15
14
  requires_review: "requires_review";
15
+ failed: "failed";
16
16
  queued: "queued";
17
17
  not_currently_installed: "not_currently_installed";
18
18
  accepted_for_retry: "accepted_for_retry";
@@ -45,7 +45,7 @@ export declare class HostedServerModerationClient implements DeveloperServerMode
45
45
  idempotencyKey: string;
46
46
  }): Promise<{
47
47
  operationId: string;
48
- outcome: "completed" | "failed" | "requires_review" | "queued" | "not_currently_installed" | "accepted_for_retry" | "already_blacklisted";
48
+ outcome: "completed" | "requires_review" | "failed" | "queued" | "not_currently_installed" | "accepted_for_retry" | "already_blacklisted";
49
49
  paidSlotsReleased: number;
50
50
  }>;
51
51
  }
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, EckoOperationsRepository, inspectConfiguredModuleMigrations, inspectCoreMigrations, inspectCreatorOnboardingMigrations, runCreatorOnboardingMigrations, PremiumAllocationRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
4
+ import { checkDatabaseConnection, createDatabasePool, EckoOperationsRepository, InstallationRepository, inspectConfiguredModuleMigrations, inspectCoreMigrations, inspectCreatorOnboardingMigrations, runCreatorOnboardingMigrations, PremiumAllocationRepository, runCoreMigrations, runConfiguredModuleMigrations, } from "@helyx/database";
5
5
  import { createRuntime } from "@helyx/framework";
6
6
  import { assertRepositoryEngineSupported } from "@helyx/database/adapters";
7
7
  import { createPlatformServices, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, PlatformMemberRoleGrantOwnershipService, PostgresMemberRoleGrantOwnershipStore, } from "@helyx/platform";
@@ -20,6 +20,7 @@ import { assertConfiguredModuleDatabaseCompatibility, loadConfiguredModulePackag
20
20
  import { resolveModulePackageSpecifiers } from "./module-configuration.js";
21
21
  import { createPermissionsModule } from "./permissions-module.js";
22
22
  import { createManagementModule } from "./management-module.js";
23
+ import { createDiagnosticModule, createMysqlDiagnosticLock, createPostgresDiagnosticLock, } from "./diagnostic-command.js";
23
24
  import { DiscordMessageDeliveryService, DiscordMemberRoleService, DiscordVerificationMemberService, DiscordResourceInspection, installModuleEvents, } from "./events.js";
24
25
  import { DiscordModerationMemberEnforcementAdapter } from "./discord-member-enforcement-service.js";
25
26
  import { DiscordModuleLoggingService } from "./module-logging.js";
@@ -303,6 +304,30 @@ async function startUnshardedBot(processEnvironment) {
303
304
  });
304
305
  const permissionsModule = createPermissionsModule(featureModules);
305
306
  const managementModule = createManagementModule(featureModules, guildResources);
307
+ const diagnosticInstallations = mysql
308
+ ? new (await import("@helyx/database-mysql")).InstallationRepository(mysql.pool)
309
+ : new InstallationRepository(database);
310
+ const diagnosticModule = createDiagnosticModule({
311
+ client,
312
+ modules: featureModules,
313
+ runExclusive: mysql
314
+ ? createMysqlDiagnosticLock(mysql.pool)
315
+ : createPostgresDiagnosticLock(database),
316
+ assertGuildAvailable: async (guildId) => {
317
+ if (shuttingDown ||
318
+ !(await diagnosticInstallations.getActiveGuild(guildId)))
319
+ throw new Error("The server installation is not available");
320
+ },
321
+ listEnabledModules: (guildId) => diagnosticInstallations.listEnabledModules(guildId),
322
+ commandStatus: {
323
+ get: (guildId) => {
324
+ return guildCommandReads.getStatus(guildId);
325
+ },
326
+ refresh: (input) => {
327
+ return guildCommandReads.refreshStatus(input);
328
+ },
329
+ },
330
+ });
306
331
  const cloudManagementModule = environment.HELYX_CLOUD_CONNECT_ENABLED
307
332
  ? createCloudManagementModule({
308
333
  connector: cloudConnector,
@@ -327,6 +352,7 @@ async function startUnshardedBot(processEnvironment) {
327
352
  : undefined;
328
353
  const publicModules = [
329
354
  managementModule,
355
+ diagnosticModule,
330
356
  permissionsModule,
331
357
  supportModule,
332
358
  privacyModule,
@@ -405,6 +431,7 @@ async function startUnshardedBot(processEnvironment) {
405
431
  ...(deployedCommit ? { deploymentReference: deployedCommit } : {}),
406
432
  coreFunctions: [
407
433
  managementModule,
434
+ diagnosticModule,
408
435
  permissionsModule,
409
436
  supportModule,
410
437
  privacyModule,
@@ -534,6 +561,7 @@ async function startUnshardedBot(processEnvironment) {
534
561
  });
535
562
  const enabledCoreModuleIds = new Set([
536
563
  managementModule.manifest.id,
564
+ diagnosticModule.manifest.id,
537
565
  permissionsModule.manifest.id,
538
566
  supportModule.manifest.id,
539
567
  ...(premiumActivationModule ? [premiumActivationModule.manifest.id] : []),
@@ -555,6 +583,7 @@ async function startUnshardedBot(processEnvironment) {
555
583
  synchroniseInternalGuild: false,
556
584
  prepare: prepareGuilds,
557
585
  selfAuthorizingGuildModuleIds: new Set([
586
+ diagnosticModule.manifest.id,
558
587
  supportModule.manifest.id,
559
588
  ...(premiumActivationModule
560
589
  ? [premiumActivationModule.manifest.id]
@@ -286,6 +286,35 @@ export declare const shardControlOperationSchema: z.ZodDiscriminatedUnion<[z.Zod
286
286
  moduleIds: z.ZodArray<z.ZodString>;
287
287
  expectedRevision: z.ZodString;
288
288
  idempotencyKey: z.ZodString;
289
+ }, z.core.$strict>, z.ZodObject<{
290
+ actorUserId: z.ZodString;
291
+ correlationId: z.ZodString;
292
+ kind: z.ZodLiteral<"guild.module.channel-audit.config.read">;
293
+ guildId: z.ZodString;
294
+ moduleId: z.ZodEnum<{
295
+ "helyx.account-gate": "helyx.account-gate";
296
+ "helyx.verification": "helyx.verification";
297
+ }>;
298
+ }, z.core.$strict>, z.ZodObject<{
299
+ actorUserId: z.ZodString;
300
+ correlationId: z.ZodString;
301
+ kind: z.ZodLiteral<"guild.module.channel-audit.check">;
302
+ guildId: z.ZodString;
303
+ moduleId: z.ZodEnum<{
304
+ "helyx.account-gate": "helyx.account-gate";
305
+ "helyx.verification": "helyx.verification";
306
+ }>;
307
+ assumptions: z.ZodObject<{
308
+ entryChannelId: z.ZodString;
309
+ beforeRoleIds: z.ZodArray<z.ZodString>;
310
+ afterRoleId: z.ZodString;
311
+ entrance: z.ZodEnum<{
312
+ public: "public";
313
+ restricted: "restricted";
314
+ }>;
315
+ allowedChannelIds: z.ZodArray<z.ZodString>;
316
+ reviewChannelId: z.ZodOptional<z.ZodString>;
317
+ }, z.core.$strict>;
289
318
  }, z.core.$strict>, z.ZodObject<{
290
319
  actorUserId: z.ZodString;
291
320
  correlationId: z.ZodString;
@@ -742,6 +771,35 @@ export declare const shardControlRequestSchema: z.ZodObject<{
742
771
  moduleIds: z.ZodArray<z.ZodString>;
743
772
  expectedRevision: z.ZodString;
744
773
  idempotencyKey: z.ZodString;
774
+ }, z.core.$strict>, z.ZodObject<{
775
+ actorUserId: z.ZodString;
776
+ correlationId: z.ZodString;
777
+ kind: z.ZodLiteral<"guild.module.channel-audit.config.read">;
778
+ guildId: z.ZodString;
779
+ moduleId: z.ZodEnum<{
780
+ "helyx.account-gate": "helyx.account-gate";
781
+ "helyx.verification": "helyx.verification";
782
+ }>;
783
+ }, z.core.$strict>, z.ZodObject<{
784
+ actorUserId: z.ZodString;
785
+ correlationId: z.ZodString;
786
+ kind: z.ZodLiteral<"guild.module.channel-audit.check">;
787
+ guildId: z.ZodString;
788
+ moduleId: z.ZodEnum<{
789
+ "helyx.account-gate": "helyx.account-gate";
790
+ "helyx.verification": "helyx.verification";
791
+ }>;
792
+ assumptions: z.ZodObject<{
793
+ entryChannelId: z.ZodString;
794
+ beforeRoleIds: z.ZodArray<z.ZodString>;
795
+ afterRoleId: z.ZodString;
796
+ entrance: z.ZodEnum<{
797
+ public: "public";
798
+ restricted: "restricted";
799
+ }>;
800
+ allowedChannelIds: z.ZodArray<z.ZodString>;
801
+ reviewChannelId: z.ZodOptional<z.ZodString>;
802
+ }, z.core.$strict>;
745
803
  }, z.core.$strict>, z.ZodObject<{
746
804
  actorUserId: z.ZodString;
747
805
  correlationId: z.ZodString;
@@ -1703,6 +1761,35 @@ export declare const supervisorMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObje
1703
1761
  moduleIds: z.ZodArray<z.ZodString>;
1704
1762
  expectedRevision: z.ZodString;
1705
1763
  idempotencyKey: z.ZodString;
1764
+ }, z.core.$strict>, z.ZodObject<{
1765
+ actorUserId: z.ZodString;
1766
+ correlationId: z.ZodString;
1767
+ kind: z.ZodLiteral<"guild.module.channel-audit.config.read">;
1768
+ guildId: z.ZodString;
1769
+ moduleId: z.ZodEnum<{
1770
+ "helyx.account-gate": "helyx.account-gate";
1771
+ "helyx.verification": "helyx.verification";
1772
+ }>;
1773
+ }, z.core.$strict>, z.ZodObject<{
1774
+ actorUserId: z.ZodString;
1775
+ correlationId: z.ZodString;
1776
+ kind: z.ZodLiteral<"guild.module.channel-audit.check">;
1777
+ guildId: z.ZodString;
1778
+ moduleId: z.ZodEnum<{
1779
+ "helyx.account-gate": "helyx.account-gate";
1780
+ "helyx.verification": "helyx.verification";
1781
+ }>;
1782
+ assumptions: z.ZodObject<{
1783
+ entryChannelId: z.ZodString;
1784
+ beforeRoleIds: z.ZodArray<z.ZodString>;
1785
+ afterRoleId: z.ZodString;
1786
+ entrance: z.ZodEnum<{
1787
+ public: "public";
1788
+ restricted: "restricted";
1789
+ }>;
1790
+ allowedChannelIds: z.ZodArray<z.ZodString>;
1791
+ reviewChannelId: z.ZodOptional<z.ZodString>;
1792
+ }, z.core.$strict>;
1706
1793
  }, z.core.$strict>, z.ZodObject<{
1707
1794
  actorUserId: z.ZodString;
1708
1795
  correlationId: z.ZodString;
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { channelAuditModuleIdSchema, channelAuditInputSchema, channelAuditConfigurationSchema, channelAuditReportSchema, } from "@helyx/control-protocol/channel-audit";
2
3
  import { CLOUD_MAX_RESOURCE_IMPORT_BYTES, creatorOnboardingRequestSchema, } from "@helyx/control-protocol";
3
4
  import { AUTOMOD_SCANNER_LIMITS } from "@helyx/sdk";
4
5
  import { signedAssessmentEnvelopeSchema } from "@helyx/verification-contracts";
@@ -355,6 +356,23 @@ export const shardControlOperationSchema = z.discriminatedUnion("kind", [
355
356
  ...mutationContext.shape,
356
357
  })
357
358
  .strict(),
359
+ z
360
+ .object({
361
+ kind: z.literal("guild.module.channel-audit.config.read"),
362
+ guildId: snowflake,
363
+ moduleId: channelAuditModuleIdSchema,
364
+ ...mutationContext.shape,
365
+ })
366
+ .strict(),
367
+ z
368
+ .object({
369
+ kind: z.literal("guild.module.channel-audit.check"),
370
+ guildId: snowflake,
371
+ moduleId: channelAuditModuleIdSchema,
372
+ assumptions: channelAuditInputSchema,
373
+ ...mutationContext.shape,
374
+ })
375
+ .strict(),
358
376
  z
359
377
  .object({
360
378
  kind: z.literal("guild.module.configuration.update"),
@@ -1117,6 +1135,27 @@ export async function executeShardControlOperation(handler, operation) {
1117
1135
  correlationId: operation.correlationId,
1118
1136
  });
1119
1137
  }
1138
+ case "guild.module.channel-audit.config.read": {
1139
+ if (!handler.readChannelAuditConfiguration)
1140
+ throw new Error("Channel audit is not supported by this shard version");
1141
+ return channelAuditConfigurationSchema.parse(await handler.readChannelAuditConfiguration({
1142
+ guildId: operation.guildId,
1143
+ moduleId: operation.moduleId,
1144
+ actorUserId: operation.actorUserId,
1145
+ correlationId: operation.correlationId,
1146
+ }));
1147
+ }
1148
+ case "guild.module.channel-audit.check": {
1149
+ if (!handler.checkChannelAudit)
1150
+ throw new Error("Channel audit is not supported by this shard version");
1151
+ return channelAuditReportSchema.parse(await handler.checkChannelAudit({
1152
+ guildId: operation.guildId,
1153
+ moduleId: operation.moduleId,
1154
+ actorUserId: operation.actorUserId,
1155
+ correlationId: operation.correlationId,
1156
+ assumptions: operation.assumptions,
1157
+ }));
1158
+ }
1120
1159
  case "guild.module.configuration.update": {
1121
1160
  return handler.updateConfiguration({
1122
1161
  guildId: operation.guildId,
@@ -48,6 +48,8 @@ export declare class ShardControlRouter implements BotControlHandler {
48
48
  listGuildPresence(input: Parameters<BotControlHandler["listGuildPresence"]>[0]): ReturnType<BotControlHandler["listGuildPresence"]>;
49
49
  setModuleStatus(input: Parameters<BotControlHandler["setModuleStatus"]>[0]): ReturnType<BotControlHandler["setModuleStatus"]>;
50
50
  completeFirstTimeCommandSetup(input: Parameters<BotControlHandler["completeFirstTimeCommandSetup"]>[0]): ReturnType<BotControlHandler["completeFirstTimeCommandSetup"]>;
51
+ readChannelAuditConfiguration(input: Parameters<NonNullable<BotControlHandler["readChannelAuditConfiguration"]>>[0]): ReturnType<NonNullable<BotControlHandler["readChannelAuditConfiguration"]>>;
52
+ checkChannelAudit(input: Parameters<NonNullable<BotControlHandler["checkChannelAudit"]>>[0]): ReturnType<NonNullable<BotControlHandler["checkChannelAudit"]>>;
51
53
  updateConfiguration(input: Parameters<BotControlHandler["updateConfiguration"]>[0]): ReturnType<BotControlHandler["updateConfiguration"]>;
52
54
  previewConfiguration(input: Parameters<BotControlHandler["previewConfiguration"]>[0]): ReturnType<BotControlHandler["previewConfiguration"]>;
53
55
  updatePermissions(input: Parameters<BotControlHandler["updatePermissions"]>[0]): ReturnType<BotControlHandler["updatePermissions"]>;
@@ -117,6 +117,18 @@ export class ShardControlRouter {
117
117
  expectedRevision: input.expectedRevision.toString(),
118
118
  });
119
119
  }
120
+ readChannelAuditConfiguration(input) {
121
+ return this.#route(shardControlOperationSchema.parse({
122
+ kind: "guild.module.channel-audit.config.read",
123
+ ...input,
124
+ }));
125
+ }
126
+ checkChannelAudit(input) {
127
+ return this.#route(shardControlOperationSchema.parse({
128
+ kind: "guild.module.channel-audit.check",
129
+ ...input,
130
+ }));
131
+ }
120
132
  updateConfiguration(input) {
121
133
  return this.#route(shardControlOperationSchema.parse({
122
134
  kind: "guild.module.configuration.update",
@@ -13,6 +13,7 @@ import { CloudConnectorController, } from "./cloud-connector.js";
13
13
  import { createCloudManagementModule } from "./cloud-management-module.js";
14
14
  import { BotControlService, createBotControlServer, mapBotControlError, } from "./control-server.js";
15
15
  import { createManagementModule } from "./management-module.js";
16
+ import { createDiagnosticModule } from "./diagnostic-command.js";
16
17
  import { buildApplicationCommandDefinitions } from "./interactions.js";
17
18
  import { resolveModulePackageSpecifiers } from "./module-configuration.js";
18
19
  import { deriveSecureRandomSelectionKey, loadConfiguredModulePackages, platformServiceOptions, secureRandomSelectionWorkerKeyName, } from "./modules.js";
@@ -217,6 +218,10 @@ export async function startShardSupervisor(environment, processEnvironment, work
217
218
  };
218
219
  const permissionsModule = createPermissionsModule(featureModules);
219
220
  const managementModule = createManagementModule(featureModules, unavailableResources);
221
+ // The supervisor registers metadata; guild checks and repairs run in workers.
222
+ const diagnosticModule = createDiagnosticModule({
223
+ modules: featureModules,
224
+ });
220
225
  const cloudManagementModule = environment.HELYX_CLOUD_CONNECT_ENABLED
221
226
  ? createCloudManagementModule({
222
227
  connector: cloudConnector,
@@ -242,6 +247,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
242
247
  : undefined;
243
248
  const publicModules = [
244
249
  managementModule,
250
+ diagnosticModule,
245
251
  permissionsModule,
246
252
  supportModule,
247
253
  privacyModule,
@@ -265,6 +271,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
265
271
  : {}),
266
272
  coreFunctions: [
267
273
  managementModule,
274
+ diagnosticModule,
268
275
  permissionsModule,
269
276
  supportModule,
270
277
  privacyModule,
@@ -102,6 +102,7 @@ export declare function shardWorkerCommandRegistration(input: {
102
102
  prepare: NonNullable<CommandRegistration["prepare"]>;
103
103
  operationsModuleId?: string;
104
104
  supportModuleId: string;
105
+ diagnosticModuleId?: string;
105
106
  premiumActivationModuleId?: string;
106
107
  oauthModuleId?: string;
107
108
  }): CommandRegistration;