@helyx/bot 1.5.2 → 1.5.4
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 +27 -0
- package/command-root-catalogue.json +452 -0
- package/dist/cached-message-deletion-authority.d.ts +27 -0
- package/dist/cached-message-deletion-authority.js +288 -0
- package/dist/channel-audit-configuration.d.ts +11 -1
- package/dist/channel-audit-configuration.js +28 -6
- package/dist/channel-audit-control.js +42 -2
- package/dist/channel-audit-permissions.js +76 -3
- package/dist/cloud-command-policy-sync.d.ts +26 -0
- package/dist/cloud-command-policy-sync.js +198 -0
- package/dist/cloud-connector.d.ts +6 -1
- package/dist/cloud-connector.js +224 -53
- package/dist/cloud-control-dispatcher.js +18 -0
- package/dist/command-policy-runtime.d.ts +23 -0
- package/dist/command-policy-runtime.js +53 -0
- package/dist/command-root-catalogue.d.ts +5 -0
- package/dist/command-root-catalogue.js +16 -0
- package/dist/control-error-mapping.d.ts +8 -0
- package/dist/control-error-mapping.js +60 -0
- package/dist/control-errors.d.ts +3 -1
- package/dist/control-errors.js +4 -1
- package/dist/control-health-routes.d.ts +10 -0
- package/dist/control-health-routes.js +28 -0
- package/dist/control-server-schemas.d.ts +7 -0
- package/dist/control-server-schemas.js +9 -0
- package/dist/control-server.d.ts +27 -6
- package/dist/control-server.js +103 -122
- package/dist/dashboard-feature-control.d.ts +2 -1
- package/dist/dashboard-feature-control.js +1 -0
- package/dist/dashboard-logging.d.ts +20 -0
- package/dist/dashboard-logging.js +39 -0
- package/dist/dashboard-module-projection.d.ts +1 -1
- package/dist/dashboard-module-projection.js +5 -2
- package/dist/dashboard-preview-import-routes.js +8 -0
- package/dist/database-bootstrap.d.ts +1 -1
- package/dist/database-bootstrap.js +4 -3
- package/dist/discord-channel-audit.js +1 -0
- package/dist/discord-user-identities.d.ts +14 -0
- package/dist/discord-user-identities.js +64 -0
- package/dist/emergency-deletion-dispatcher.d.ts +68 -0
- package/dist/emergency-deletion-dispatcher.js +502 -0
- package/dist/emergency-deletion-rest-transport.d.ts +24 -0
- package/dist/emergency-deletion-rest-transport.js +253 -0
- package/dist/events.d.ts +5 -0
- package/dist/events.js +53 -10
- package/dist/guarded-discord-dispatcher.d.ts +29 -0
- package/dist/guarded-discord-dispatcher.js +204 -0
- package/dist/guild-command-discord-client.d.ts +2 -0
- package/dist/guild-command-discord-client.js +7 -0
- package/dist/guild-command-projection.d.ts +4 -1
- package/dist/guild-command-projection.js +25 -0
- package/dist/guild-command-read-service.d.ts +10 -1
- package/dist/guild-command-read-service.js +37 -0
- package/dist/guild-command-worker.d.ts +19 -4
- package/dist/guild-command-worker.js +356 -26
- package/dist/guild-message-protection-author-roles.d.ts +7 -0
- package/dist/guild-message-protection-author-roles.js +93 -0
- package/dist/guild-message-protection-deletion.d.ts +21 -0
- package/dist/guild-message-protection-deletion.js +138 -0
- package/dist/guild-message-protection-delivery.d.ts +41 -0
- package/dist/guild-message-protection-delivery.js +223 -0
- package/dist/guild-message-update-metadata.d.ts +32 -0
- package/dist/guild-message-update-metadata.js +255 -0
- package/dist/guilds.d.ts +2 -1
- package/dist/guilds.js +11 -1
- package/dist/hosted-server-moderation.d.ts +2 -2
- package/dist/index.js +68 -4
- package/dist/interactions.d.ts +3 -1
- package/dist/interactions.js +77 -12
- package/dist/invite-rollup-maintenance.d.ts +9 -0
- package/dist/invite-rollup-maintenance.js +12 -0
- package/dist/managed-resource-authorization.d.ts +15 -0
- package/dist/managed-resource-authorization.js +37 -0
- package/dist/managed-resource-create-defaults.d.ts +21 -0
- package/dist/managed-resource-create-defaults.js +83 -0
- package/dist/managed-resource-query.d.ts +6 -0
- package/dist/managed-resource-query.js +15 -0
- package/dist/member-role-ids.d.ts +17 -0
- package/dist/member-role-ids.js +66 -0
- package/dist/message-protection-admission.d.ts +60 -0
- package/dist/message-protection-admission.js +279 -0
- package/dist/message-protection-cold-data.d.ts +42 -0
- package/dist/message-protection-cold-data.js +180 -0
- package/dist/message-protection-coordinator.d.ts +73 -0
- package/dist/message-protection-coordinator.js +622 -0
- package/dist/message-protection-settings.d.ts +40 -0
- package/dist/message-protection-settings.js +275 -0
- package/dist/message-protection-summary.d.ts +8 -0
- package/dist/message-protection-summary.js +98 -0
- package/dist/module-availability-control.js +20 -12
- package/dist/module-logging.d.ts +3 -2
- package/dist/module-logging.js +6 -1
- package/dist/modules.js +56 -1
- package/dist/mysql-runtime-persistence.d.ts +1 -0
- package/dist/mysql-runtime-persistence.js +4 -1
- package/dist/nexus-command-recovery-control-routes.d.ts +5 -0
- package/dist/nexus-command-recovery-control-routes.js +48 -0
- package/dist/nexus-command-references.d.ts +6 -0
- package/dist/nexus-command-references.js +33 -0
- package/dist/scheduled-task-runner.js +23 -4
- package/dist/shard-command-startup.d.ts +13 -0
- package/dist/shard-command-startup.js +27 -0
- package/dist/shard-contracts.d.ts +84 -0
- package/dist/shard-contracts.js +33 -0
- package/dist/shard-core-readiness.d.ts +5 -0
- package/dist/shard-core-readiness.js +19 -0
- package/dist/shard-router.d.ts +3 -0
- package/dist/shard-router.js +10 -1
- package/dist/shard-supervisor.d.ts +1 -1
- package/dist/shard-supervisor.js +124 -43
- package/dist/shard-worker.js +25 -15
- package/documentation/README.md +1 -0
- package/documentation/examples/.env.example +0 -45
- package/documentation/guides/self-hosting.md +54 -0
- package/package.json +18 -17
package/dist/shard-supervisor.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { safeError, ServiceRegistry } from "@helyx/core";
|
|
2
2
|
import { checkDatabaseConnection, createDatabasePool, AuditRepository, GuildCommandRepository, HostedServerSafetyRepository, InstallationRepository, inspectConfiguredModuleMigrations, inspectCoreMigrations, inspectCreatorOnboardingMigrations, runCreatorOnboardingMigrations, InteractionSessionRepository, EckoOperationsRepository, PremiumAllocationRepository, RateLimitRepository, runConfiguredModuleMigrations, runCoreMigrations, RuntimeStateRepository, } from "@helyx/database";
|
|
3
3
|
import { assertRepositoryEngineSupported } from "@helyx/database/adapters";
|
|
4
|
-
import { createPlatformServices, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, } from "@helyx/platform";
|
|
5
|
-
import { AUTOMOD_ENHANCED_CONTRACT_VERSION, AUTOMOD_ENHANCED_PROVIDER_ID, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
4
|
+
import { createPlatformServices, createPostgresCommandRestrictionRecovery, createPostgresNexusCommandRecovery, createPostgresCommandCatalogueRecovery, createPostgresCommandPolicyDelivery, gamingStatisticsRecordOwner, achievementContributionRecordOwner, PostgresPrivacyProviderExecutor, } from "@helyx/platform";
|
|
5
|
+
import { AUTOMOD_ENHANCED_CONTRACT_VERSION, AUTOMOD_ENHANCED_PROVIDER_ID, HELYX_SERVICE_NAMES, collectReservedCommandNames, } from "@helyx/sdk";
|
|
6
6
|
import { Client, ShardingManager } from "discord.js";
|
|
7
7
|
import { createHash, randomUUID } from "node:crypto";
|
|
8
8
|
import { createRequire } from "node:module";
|
|
9
|
+
import { loadBundledCommandCatalogue } from "./command-root-catalogue.js";
|
|
10
|
+
import { completeShardCommandStartup, shardStartupCatalogueReady, } from "./shard-command-startup.js";
|
|
9
11
|
import { join } from "node:path";
|
|
10
12
|
import pino, {} from "pino";
|
|
11
13
|
import { synchroniseApplicationCommands, } from "./command-sync.js";
|
|
12
|
-
import { CloudConnectorController
|
|
14
|
+
import { CloudConnectorController } from "./cloud-connector.js";
|
|
15
|
+
import { commandPolicyLeaderIsCurrent, captureShardedCommandPolicyAdmission, shardCommandPolicyScope, } from "./command-policy-runtime.js";
|
|
16
|
+
import { allocateShardDeploymentOrder, prepareShardDeploymentOrder, } from "./shard-core-readiness.js";
|
|
13
17
|
import { createCloudManagementModule } from "./cloud-management-module.js";
|
|
14
18
|
import { BotControlService, createBotControlServer, mapBotControlError, } from "./control-server.js";
|
|
15
19
|
import { createManagementModule } from "./management-module.js";
|
|
@@ -131,6 +135,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
131
135
|
let numericRegistrations = [];
|
|
132
136
|
let leaderLease;
|
|
133
137
|
let cloudConnector;
|
|
138
|
+
let commandPolicyRuntime;
|
|
134
139
|
let cloudStarted = false;
|
|
135
140
|
let maintainingLeases = false;
|
|
136
141
|
let rebalancingShards = false;
|
|
@@ -164,6 +169,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
164
169
|
let guildCommandReleasePromise;
|
|
165
170
|
let scheduledModules = [];
|
|
166
171
|
let commandModules = [];
|
|
172
|
+
let commandCatalogue;
|
|
167
173
|
let workersRoutable = false;
|
|
168
174
|
let promoteToLeader = () => Promise.resolve(false);
|
|
169
175
|
try {
|
|
@@ -173,6 +179,15 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
173
179
|
const modulePackages = await loadConfiguredModulePackages(moduleSpecifiers);
|
|
174
180
|
const featureModules = modulePackages.map(({ definition }) => definition);
|
|
175
181
|
assertReviewedPremiumCatalogue(featureModules);
|
|
182
|
+
const bundledCommandCatalogue = featureModules.some(({ manifest }) => manifest.id === "helyx.nexus-core")
|
|
183
|
+
? await loadBundledCommandCatalogue()
|
|
184
|
+
: null;
|
|
185
|
+
const catalogueIdentity = bundledCommandCatalogue
|
|
186
|
+
? {
|
|
187
|
+
revision: bundledCommandCatalogue.revision,
|
|
188
|
+
fingerprint: bundledCommandCatalogue.fingerprint,
|
|
189
|
+
}
|
|
190
|
+
: null;
|
|
176
191
|
const gateway = await fetchGatewayBotInformation(environment.DISCORD_TOKEN);
|
|
177
192
|
shardCount =
|
|
178
193
|
environment.HELYX_SHARD_MODE === "fixed"
|
|
@@ -301,11 +316,57 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
301
316
|
listAvailabilityModules: () => availabilityCatalogue(availabilityModules, statusModules, runtimeAvailability),
|
|
302
317
|
};
|
|
303
318
|
const initialiseLeader = async () => {
|
|
304
|
-
|
|
319
|
+
let commandPolicyAdmission;
|
|
320
|
+
let policyLeader;
|
|
321
|
+
let policyOwnerCurrent;
|
|
322
|
+
await runLeaderStartup(database, environment, modulePackages, modules, logger, {
|
|
323
|
+
catalogue: catalogueIdentity,
|
|
324
|
+
assertOwnership: assertStartupLeaseHealthy,
|
|
325
|
+
captureCommandPolicy: async () => {
|
|
326
|
+
if (environment.HELYX_RUNTIME_PROFILE !== "self-hosted")
|
|
327
|
+
return;
|
|
328
|
+
const leader = leaderLease;
|
|
329
|
+
if (!leader)
|
|
330
|
+
throw new Error("Command policy requires the current shard leader");
|
|
331
|
+
const delivery = createPostgresCommandPolicyDelivery(database, {
|
|
332
|
+
applicationId: environment.DISCORD_CLIENT_ID,
|
|
333
|
+
scope: shardCommandPolicyScope(generation, deploymentOrder),
|
|
334
|
+
});
|
|
335
|
+
policyLeader = leader;
|
|
336
|
+
policyOwnerCurrent = coordinator
|
|
337
|
+
? () => withTimeout(commandPolicyLeaderIsCurrent({
|
|
338
|
+
coordinator: coordinator,
|
|
339
|
+
descriptor: generationDescriptor,
|
|
340
|
+
deploymentOrder,
|
|
341
|
+
leader,
|
|
342
|
+
}), LEASE_VALIDATION_TIMEOUT_MS)
|
|
343
|
+
: () => Promise.resolve(true);
|
|
344
|
+
commandPolicyAdmission = await captureShardedCommandPolicyAdmission(delivery, leader.supervisorId);
|
|
345
|
+
},
|
|
346
|
+
prepareCommandPolicy: async () => {
|
|
347
|
+
if (environment.HELYX_RUNTIME_PROFILE !== "self-hosted")
|
|
348
|
+
return;
|
|
349
|
+
if (!commandPolicyAdmission || !policyLeader || !policyOwnerCurrent)
|
|
350
|
+
throw new Error("Command policy admission was not captured at database readiness");
|
|
351
|
+
commandPolicyRuntime = await commandPolicyAdmission.claim(String(policyLeader.fencingToken), policyOwnerCurrent);
|
|
352
|
+
await cloudConnector?.setCommandPolicyDelivery(commandPolicyRuntime);
|
|
353
|
+
},
|
|
354
|
+
publishCatalogue: async () => {
|
|
355
|
+
if (!bundledCommandCatalogue)
|
|
356
|
+
return;
|
|
357
|
+
commandCatalogue = createPostgresCommandCatalogueRecovery(database, environment.DISCORD_CLIENT_ID);
|
|
358
|
+
await commandCatalogue.publish(bundledCommandCatalogue);
|
|
359
|
+
},
|
|
360
|
+
});
|
|
305
361
|
assertStartupLeaseHealthy();
|
|
306
362
|
privacyDatabase = await createPrivacyDatabase(environment, database);
|
|
307
363
|
assertStartupLeaseHealthy();
|
|
308
|
-
const services = new Map(createPlatformServices(database, publicModules,
|
|
364
|
+
const services = new Map(createPlatformServices(database, publicModules, {
|
|
365
|
+
...platformServiceOptions(environment, publicModules),
|
|
366
|
+
...(commandPolicyRuntime
|
|
367
|
+
? { commandPolicyDeliveryScope: commandPolicyRuntime.owner.scope }
|
|
368
|
+
: {}),
|
|
369
|
+
}));
|
|
309
370
|
runtimeAvailability = services.get(HELYX_SERVICE_NAMES.moduleAvailability);
|
|
310
371
|
leaderScannerRegistry = new BotAutoModerationScannerRegistry(publicModules, new ServiceRegistry(services), "hosted", undefined, environment.HELYX_DEPLOYMENT_ENVIRONMENT);
|
|
311
372
|
if (numericWriteBehind)
|
|
@@ -350,9 +411,9 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
350
411
|
environment: environment.NODE_ENV,
|
|
351
412
|
generation,
|
|
352
413
|
shardCount,
|
|
353
|
-
catalogueHash: shardRuntimeCompositionHash(modules, environment.DISCORD_INTERNAL_GUILD_ID, environment.HELYX_GLOBAL_COMMAND_MODE),
|
|
414
|
+
catalogueHash: shardRuntimeCompositionHash(modules, environment.DISCORD_INTERNAL_GUILD_ID, environment.HELYX_GLOBAL_COMMAND_MODE, bundledCommandCatalogue?.fingerprint),
|
|
354
415
|
};
|
|
355
|
-
deploymentOrder = await
|
|
416
|
+
deploymentOrder = await prepareShardDeploymentOrder(database, environment.HELYX_DATABASE_STARTUP_MODE);
|
|
356
417
|
if (environment.HELYX_SHARD_SCOPE === "coordinated") {
|
|
357
418
|
coordinator = new RedisShardCoordinator(environment.HELYX_SHARD_REDIS_URL, environment.HELYX_SHARD_REDIS_NAMESPACE, {
|
|
358
419
|
onError: (error) => logger.error({ event: "shard.redis_error", error: safeError(error) }, "Shard coordination failed"),
|
|
@@ -417,7 +478,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
417
478
|
await initialiseLeader();
|
|
418
479
|
}
|
|
419
480
|
else {
|
|
420
|
-
await waitForLeaderStartup(database, generation);
|
|
481
|
+
await waitForLeaderStartup(database, generation, catalogueIdentity);
|
|
421
482
|
assertStartupLeaseHealthy();
|
|
422
483
|
}
|
|
423
484
|
const router = new ShardControlRouter({
|
|
@@ -484,6 +545,15 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
484
545
|
: undefined;
|
|
485
546
|
controlServer = await createBotControlServer({
|
|
486
547
|
service: router,
|
|
548
|
+
...(commandModules.some(({ manifest }) => manifest.id === "helyx.nexus-core")
|
|
549
|
+
? {
|
|
550
|
+
nexusCommandRecovery: createPostgresNexusCommandRecovery(database, {
|
|
551
|
+
applicationId: environment.DISCORD_CLIENT_ID,
|
|
552
|
+
persistedCommandCatalogue: true,
|
|
553
|
+
reservedCommandNames: collectReservedCommandNames(commandModules),
|
|
554
|
+
}),
|
|
555
|
+
}
|
|
556
|
+
: {}),
|
|
487
557
|
secret: environment.HELYX_BOT_CONTROL_SECRET,
|
|
488
558
|
...shardHostedControlCallbacks(router),
|
|
489
559
|
logger: { level: environment.LOG_LEVEL },
|
|
@@ -1218,6 +1288,15 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
1218
1288
|
}
|
|
1219
1289
|
if (!guildCommandWorker) {
|
|
1220
1290
|
guildCommandWorker = new GuildCommandWorker({
|
|
1291
|
+
...(commandPolicyRuntime
|
|
1292
|
+
? { commandPolicyDelivery: commandPolicyRuntime.delivery }
|
|
1293
|
+
: {}),
|
|
1294
|
+
...(commandCatalogue ? { commandCatalogue } : {}),
|
|
1295
|
+
...(commandModules.some(({ manifest }) => manifest.id === "helyx.nexus-core")
|
|
1296
|
+
? {
|
|
1297
|
+
commandRestrictions: createPostgresCommandRestrictionRecovery(database, environment.DISCORD_CLIENT_ID),
|
|
1298
|
+
}
|
|
1299
|
+
: {}),
|
|
1221
1300
|
database,
|
|
1222
1301
|
modules: commandModules,
|
|
1223
1302
|
logger,
|
|
@@ -1540,7 +1619,10 @@ export function splitNumericCommittedBatch(batch, index) {
|
|
|
1540
1619
|
results: [result],
|
|
1541
1620
|
};
|
|
1542
1621
|
}
|
|
1543
|
-
async function runLeaderStartup(database, environment, modulePackages, modules, logger) {
|
|
1622
|
+
async function runLeaderStartup(database, environment, modulePackages, modules, logger, commandStartup) {
|
|
1623
|
+
// Core readiness and durable order were established before election. Capture
|
|
1624
|
+
// admission before any further migration/owner startup can wait asynchronously.
|
|
1625
|
+
await commandStartup.captureCommandPolicy();
|
|
1544
1626
|
const moduleMigrationSources = modulePackages.map(({ definition, packageRoot }) => ({
|
|
1545
1627
|
moduleId: definition.manifest.id,
|
|
1546
1628
|
moduleVersion: definition.manifest.version,
|
|
@@ -1566,23 +1648,36 @@ async function runLeaderStartup(database, environment, modulePackages, modules,
|
|
|
1566
1648
|
await runCoreMigrations(database);
|
|
1567
1649
|
await runConfiguredModuleMigrations(database, moduleMigrationSources);
|
|
1568
1650
|
}
|
|
1651
|
+
await commandStartup.prepareCommandPolicy();
|
|
1569
1652
|
await enforcePremiumCatalogue(database, logger);
|
|
1570
1653
|
const repository = new RuntimeStateRepository(database);
|
|
1571
|
-
await
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1654
|
+
await completeShardCommandStartup({
|
|
1655
|
+
assertOwnership: () => commandStartup.assertOwnership(),
|
|
1656
|
+
publishCatalogue: () => commandStartup.publishCatalogue(),
|
|
1657
|
+
synchronise: async () => {
|
|
1658
|
+
await synchroniseApplicationCommands({
|
|
1659
|
+
applicationId: environment.DISCORD_CLIENT_ID,
|
|
1660
|
+
token: environment.DISCORD_TOKEN,
|
|
1661
|
+
modules,
|
|
1662
|
+
globalCommandMode: environment.HELYX_GLOBAL_COMMAND_MODE,
|
|
1663
|
+
...(environment.DISCORD_INTERNAL_GUILD_ID
|
|
1664
|
+
? { internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID }
|
|
1665
|
+
: {}),
|
|
1666
|
+
// Worker-owned guild collections stay untouched while command writes are off.
|
|
1667
|
+
synchroniseInternalGuild: false,
|
|
1668
|
+
state: new RuntimeCommandHashStore(repository),
|
|
1669
|
+
});
|
|
1670
|
+
},
|
|
1671
|
+
markReady: () => setRuntimeState(repository, `shards:${environment.HELYX_SHARD_GENERATION ?? "local"}:startup`, {
|
|
1672
|
+
ready: true,
|
|
1673
|
+
completedAt: new Date().toISOString(),
|
|
1674
|
+
commandCatalogue: commandStartup.catalogue
|
|
1675
|
+
? {
|
|
1676
|
+
revision: commandStartup.catalogue.revision,
|
|
1677
|
+
fingerprint: commandStartup.catalogue.fingerprint,
|
|
1678
|
+
}
|
|
1679
|
+
: null,
|
|
1680
|
+
}),
|
|
1586
1681
|
});
|
|
1587
1682
|
logger.info({ event: "shard.singleton_startup_complete" }, "Shard singleton startup complete");
|
|
1588
1683
|
}
|
|
@@ -1619,15 +1714,12 @@ async function setRuntimeState(repository, key, value) {
|
|
|
1619
1714
|
if (!updated)
|
|
1620
1715
|
throw new Error("Runtime state changed concurrently");
|
|
1621
1716
|
}
|
|
1622
|
-
async function waitForLeaderStartup(database, generation) {
|
|
1717
|
+
async function waitForLeaderStartup(database, generation, catalogue) {
|
|
1623
1718
|
const deadline = Date.now() + STARTUP_WAIT_MS;
|
|
1624
1719
|
while (Date.now() < deadline) {
|
|
1625
1720
|
try {
|
|
1626
1721
|
const state = await new RuntimeStateRepository(database).get(`shards:${generation}:startup`);
|
|
1627
|
-
if (
|
|
1628
|
-
state.value !== null &&
|
|
1629
|
-
!Array.isArray(state.value) &&
|
|
1630
|
-
state.value.ready === true)
|
|
1722
|
+
if (shardStartupCatalogueReady(state?.value, catalogue))
|
|
1631
1723
|
return;
|
|
1632
1724
|
}
|
|
1633
1725
|
catch {
|
|
@@ -1741,11 +1833,12 @@ export function buildShardWorkerEnvironment(source, identity, restartCount) {
|
|
|
1741
1833
|
HELYX_SHARD_RESTART_COUNT: String(restartCount),
|
|
1742
1834
|
};
|
|
1743
1835
|
}
|
|
1744
|
-
export function shardRuntimeCompositionHash(modules, internalGuildId, globalCommandMode = "legacy") {
|
|
1836
|
+
export function shardRuntimeCompositionHash(modules, internalGuildId, globalCommandMode = "legacy", commandCatalogueFingerprint) {
|
|
1745
1837
|
const commands = buildApplicationCommandDefinitions(modules, internalGuildId, globalCommandMode);
|
|
1746
1838
|
return createHash("sha256")
|
|
1747
1839
|
.update(JSON.stringify({
|
|
1748
1840
|
botVersion: botVersion(),
|
|
1841
|
+
...(commandCatalogueFingerprint ? { commandCatalogueFingerprint } : {}),
|
|
1749
1842
|
modules: modules
|
|
1750
1843
|
.map(({ manifest }) => ({
|
|
1751
1844
|
id: manifest.id,
|
|
@@ -1990,19 +2083,7 @@ function sameShardLease(left, right) {
|
|
|
1990
2083
|
left.fencingToken === right.fencingToken);
|
|
1991
2084
|
}
|
|
1992
2085
|
export async function nextShardDeploymentOrder(database) {
|
|
1993
|
-
|
|
1994
|
-
try {
|
|
1995
|
-
value = await new RuntimeStateRepository(database).nextShardDeploymentOrder();
|
|
1996
|
-
}
|
|
1997
|
-
catch (error) {
|
|
1998
|
-
if (error.code !== "42P01")
|
|
1999
|
-
throw error;
|
|
2000
|
-
return 1;
|
|
2001
|
-
}
|
|
2002
|
-
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
2003
|
-
throw new Error("Shard deployment order exceeds the safe runtime range");
|
|
2004
|
-
}
|
|
2005
|
-
return Number(value);
|
|
2086
|
+
return allocateShardDeploymentOrder(database);
|
|
2006
2087
|
}
|
|
2007
2088
|
export async function renewStartupOwnership(coordinator, descriptor, deploymentOrder, leaderLease) {
|
|
2008
2089
|
if (!(await coordinator.refreshGeneration(descriptor, deploymentOrder)))
|
package/dist/shard-worker.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseBotEnvironment } from "@helyx/config";
|
|
2
|
+
import { shardCommandPolicyScope } from "./command-policy-runtime.js";
|
|
2
3
|
import { safeError, ServiceRegistry } from "@helyx/core";
|
|
3
4
|
import { checkDatabaseConnection, createDatabasePool, PremiumAllocationRepository, InstallationRepository, } from "@helyx/database";
|
|
4
5
|
import { assertRepositoryEngineSupported } from "@helyx/database/adapters";
|
|
@@ -264,6 +265,11 @@ export async function startShardWorker(processEnvironment = process.env, identit
|
|
|
264
265
|
const serviceOptions = platformServiceOptions(environment, publicModules);
|
|
265
266
|
for (const [name, service] of createPlatformServices(database, publicModules, {
|
|
266
267
|
...serviceOptions,
|
|
268
|
+
...(environment.HELYX_RUNTIME_PROFILE === "self-hosted"
|
|
269
|
+
? {
|
|
270
|
+
commandPolicyDeliveryScope: shardCommandPolicyScope(identity.generation, identity.deploymentOrder),
|
|
271
|
+
}
|
|
272
|
+
: {}),
|
|
267
273
|
recordRecoveryOwnership: {
|
|
268
274
|
mode: "shard",
|
|
269
275
|
shardId: identity.shardId,
|
|
@@ -391,21 +397,24 @@ export async function startShardWorker(processEnvironment = process.env, identit
|
|
|
391
397
|
shardCount: identity.shardCount,
|
|
392
398
|
})
|
|
393
399
|
: undefined;
|
|
394
|
-
const commandsReady = installInteractions(client, modules,
|
|
395
|
-
...(
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
400
|
+
const commandsReady = installInteractions(client, modules, {
|
|
401
|
+
...shardWorkerCommandRegistration({
|
|
402
|
+
...(environment.DISCORD_INTERNAL_GUILD_ID
|
|
403
|
+
? { internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID }
|
|
404
|
+
: {}),
|
|
405
|
+
prepare: prepareGuilds,
|
|
406
|
+
...(operationsModule
|
|
407
|
+
? { operationsModuleId: operationsModule.manifest.id }
|
|
408
|
+
: {}),
|
|
409
|
+
supportModuleId: supportModule.manifest.id,
|
|
410
|
+
diagnosticModuleId: diagnosticModule.manifest.id,
|
|
411
|
+
...(premiumActivationModule
|
|
412
|
+
? { premiumActivationModuleId: premiumActivationModule.manifest.id }
|
|
413
|
+
: {}),
|
|
414
|
+
...(oauthModule ? { oauthModuleId: oauthModule.manifest.id } : {}),
|
|
415
|
+
}),
|
|
416
|
+
resolveGuildCommandName: (input) => guildCommandReads.resolveInteractionCommand(input),
|
|
417
|
+
}, logger, serviceAccess);
|
|
409
418
|
moduleEvents = installModuleEvents(client, publicModules, logger, serviceAccess, {
|
|
410
419
|
runtimeProfile: environment.HELYX_RUNTIME_PROFILE,
|
|
411
420
|
evidenceStore: moderationPlatformServices.evidenceStore,
|
|
@@ -669,6 +678,7 @@ async function handleControl(identity, service, hostedOwnerObserver, client, hos
|
|
|
669
678
|
ok: false,
|
|
670
679
|
status: mapped?.status ?? 500,
|
|
671
680
|
message: mapped?.message ?? "The owning shard rejected the request",
|
|
681
|
+
...(mapped?.fieldError ? { fieldError: mapped.fieldError } : {}),
|
|
672
682
|
};
|
|
673
683
|
}
|
|
674
684
|
}
|
package/documentation/README.md
CHANGED
|
@@ -13,6 +13,7 @@ and module guides.
|
|
|
13
13
|
|
|
14
14
|
- [Install a self-hosted Helyx instance](guides/getting-started.md)
|
|
15
15
|
- [Understand the self-hosting boundary](guides/self-hosting.md)
|
|
16
|
+
- [Understand upcoming shared Nexus command names](guides/self-hosting.md#nexus-command-names-upcoming)
|
|
16
17
|
- [Configure the environment](reference/environment.md)
|
|
17
18
|
- [Operate installations and upgrades](runbooks/installation-and-upgrades.md)
|
|
18
19
|
- [Plan backup and recovery](runbooks/backup-and-recovery.md)
|
|
@@ -9,16 +9,6 @@ DISCORD_CLIENT_ID=12345678901234567
|
|
|
9
9
|
# Explicit privileged gateway opt-in. Keep false unless a reviewed loaded module
|
|
10
10
|
# requires message content and it is enabled in the Discord developer portal.
|
|
11
11
|
HELYX_MESSAGE_CONTENT_ENABLED=false
|
|
12
|
-
# Enhanced Auto Moderation stays inside this bot process. One worker is the
|
|
13
|
-
# recommended default (range 1-4); increase only after measuring CPU and memory.
|
|
14
|
-
# Hosted scanning uses the separate Helyx Ecko service, not these settings.
|
|
15
|
-
HELYX_AUTOMOD_EMBEDDED_WORKERS=1
|
|
16
|
-
# Optional conservative queue overrides (hard ceilings: 5000 items, 64 MiB,
|
|
17
|
-
# 100 items per server). Defaults are used when these remain commented.
|
|
18
|
-
# HELYX_AUTOMOD_EMBEDDED_QUEUE_ITEMS=5000
|
|
19
|
-
# HELYX_AUTOMOD_EMBEDDED_QUEUE_BYTES=67108864
|
|
20
|
-
# HELYX_AUTOMOD_EMBEDDED_QUEUE_ITEMS_PER_GUILD=100
|
|
21
|
-
# HELYX_AUTOMOD_EMBEDDED_REFRESH_SECONDS=300
|
|
22
12
|
# Enable for loaded member workflows such as Welcome, Access Control or Levels,
|
|
23
13
|
# with Server Members Intent also enabled in the Discord developer portal.
|
|
24
14
|
HELYX_GUILD_MEMBERS_ENABLED=false
|
|
@@ -51,10 +41,6 @@ HELYX_CLOUD_CONNECT_ENABLED=true
|
|
|
51
41
|
# trace, debug, info, warn, error, fatal or silent
|
|
52
42
|
LOG_LEVEL=info
|
|
53
43
|
|
|
54
|
-
# Guild-scoped development commands only
|
|
55
|
-
# DISCORD_INTERNAL_GUILD_ID=12345678901234567
|
|
56
|
-
# APPROVED_ROLES=12345678901234567,12345678901234568
|
|
57
|
-
|
|
58
44
|
# SHARDING: OFF BY DEFAULT
|
|
59
45
|
|
|
60
46
|
# `off` is the compatible self-host default. Keep shard-only values commented
|
|
@@ -80,12 +66,6 @@ HELYX_SHARD_MODE=off
|
|
|
80
66
|
# Additional reviewed module packages
|
|
81
67
|
# HELYX_MODULE_PACKAGES=
|
|
82
68
|
|
|
83
|
-
# Private dashboard control endpoint — configure both together
|
|
84
|
-
# HELYX_BOT_CONTROL_SECRET=replace-with-a-base64-encoded-32-byte-key
|
|
85
|
-
# HELYX_BOT_CONTROL_PORT=3001
|
|
86
|
-
# HELYX_GUILD_RECONCILIATION_INTERVAL_SECONDS=900
|
|
87
|
-
HELYX_HOSTED_MODERATION_ENABLED=false
|
|
88
|
-
|
|
89
69
|
# Guild command writes remain off until explicitly enabled for known guild IDs.
|
|
90
70
|
# For enabled-module server commands, set writes=true, retain allowlist scope,
|
|
91
71
|
# add comma-separated Discord server IDs below, and use global mode platform-only.
|
|
@@ -96,14 +76,6 @@ HELYX_GUILD_COMMAND_WRITE_SCOPE=allowlist
|
|
|
96
76
|
HELYX_GLOBAL_COMMAND_MODE=legacy
|
|
97
77
|
# HELYX_GUILD_COMMAND_WRITE_GUILD_IDS=
|
|
98
78
|
|
|
99
|
-
# Alternative Cloud Connect enrolment — the Discord command is preferred
|
|
100
|
-
# HELYX_CLOUD_ENROLMENT_TOKEN=replace-with-a-short-lived-enrolment-code
|
|
101
|
-
# HELYX_CLOUD_ENROLMENT_FILE=./helyx.cloud-enrolment.json
|
|
102
|
-
|
|
103
|
-
# Hosted OAuth bridge. Keep disabled unless the API and bot share this secret.
|
|
104
|
-
HELYX_OAUTH_ENABLED=false
|
|
105
|
-
# HELYX_OAUTH_INTERNAL_SECRET=replace-with-a-base64-encoded-32-byte-key
|
|
106
|
-
|
|
107
79
|
# DANGER ZONE — HELYX-MANAGED VALUES, DO NOT EDIT
|
|
108
80
|
|
|
109
81
|
# Generated module selection
|
|
@@ -113,22 +85,5 @@ HELYX_MODULE_CATALOGUE_PATH=./helyx.modules.json
|
|
|
113
85
|
# and Invite Tracking; retain it unchanged across restarts and deployments.
|
|
114
86
|
# HELYX_DATA_ENCRYPTION_KEY=replace-with-a-base64-encoded-32-byte-key
|
|
115
87
|
|
|
116
|
-
# Official Cloud Connect endpoints
|
|
117
|
-
# HELYX_CLOUD_API_URL=https://api.helyx.gg
|
|
118
|
-
# HELYX_CLOUD_ENDPOINT=wss://connect.helyx.gg/v1/connect
|
|
119
|
-
|
|
120
|
-
# Security audit retention
|
|
121
|
-
# HELYX_CLOUD_AUDIT_RETENTION_DAYS=90
|
|
122
|
-
# HELYX_CLOUD_CREDENTIAL_AUDIT_RETENTION_DAYS=365
|
|
123
|
-
|
|
124
|
-
# Optional public status reporting. Keep disabled unless this installation has
|
|
125
|
-
# its own approved Helyx status credentials.
|
|
126
|
-
HELYX_STATUS_ENABLED=false
|
|
127
|
-
# HELYX_STATUS_URL=https://status.helyx.gg
|
|
128
|
-
# HELYX_STATUS_HEARTBEAT_SECRET=replace-with-a-base64-encoded-32-byte-key
|
|
129
|
-
# HELYX_STATUS_HEARTBEAT_INTERVAL_SECONDS=60
|
|
130
|
-
# CF_ACCESS_CLIENT_ID=replace-with-access-client-id
|
|
131
|
-
# CF_ACCESS_CLIENT_SECRET=replace-with-access-client-secret
|
|
132
|
-
|
|
133
88
|
# Runtime mode
|
|
134
89
|
NODE_ENV=production
|
|
@@ -95,6 +95,60 @@ features continue if the hosted dashboard is temporarily unavailable.
|
|
|
95
95
|
Set `HELYX_CLOUD_CONNECT_ENABLED=false` before startup when the installation
|
|
96
96
|
must remain entirely local.
|
|
97
97
|
|
|
98
|
+
## Nexus command names (upcoming)
|
|
99
|
+
|
|
100
|
+
Nexus Rewards and Nexus Economy keep `/nexus` as their default command in this
|
|
101
|
+
guide. Custom command naming is an upcoming feature, held until the current name
|
|
102
|
+
policy is configured and controlled acceptance is complete. Having a compatible
|
|
103
|
+
package or enabling Cloud Connect does not activate it.
|
|
104
|
+
|
|
105
|
+
The shared Nexus settings own one command name per Discord server. When offered
|
|
106
|
+
by an approved release, the same **Slash command name** will appear under either
|
|
107
|
+
**Modules > Nexus Rewards > Settings** or **Modules > Nexus Economy > Settings**.
|
|
108
|
+
Changing currency terminology does not change that command. A new root uses
|
|
109
|
+
1-10 lowercase ASCII letters, digits, hyphens or underscores, excluding `/`, and
|
|
110
|
+
must pass the current restricted-word and full command-reservation checks.
|
|
111
|
+
Reservations include commands from disabled modules.
|
|
112
|
+
|
|
113
|
+
In a supporting Self-Hosted release, Cloud Connect must verify the current
|
|
114
|
+
centrally managed name policy before a new custom name can be accepted. An
|
|
115
|
+
unpaired, disconnected or unsupported connection holds custom-name changes;
|
|
116
|
+
reconnection checks the latest policy before making them available again. A
|
|
117
|
+
previously stored list alone does not establish current verification. Existing
|
|
118
|
+
confirmed commands and ordinary Nexus settings remain usable. **Restore nexus**
|
|
119
|
+
can stage the default name while custom-name editing is held, subject to the
|
|
120
|
+
same confirmation, cooldown and unresolved-update checks described below.
|
|
121
|
+
|
|
122
|
+
A changed-name save requires **Confirm** or **Cancel**. Cancel, Escape or closing
|
|
123
|
+
the dialog restores only the root draft without saving the other fields.
|
|
124
|
+
Confirmation warns about the rolling 24-hour restriction, including a return to
|
|
125
|
+
`nexus`, and Discord command permission overrides which must be reapplied after
|
|
126
|
+
the update. Invalid/rejected requests create no future rename queue. Ordinary
|
|
127
|
+
settings saves, unchanged names and delivery retries do not extend the timer.
|
|
128
|
+
|
|
129
|
+
An accepted setting and a confirmed Discord command are different states. While
|
|
130
|
+
an update is pending or failed, use the previously confirmed command in Discord;
|
|
131
|
+
another customer rename remains blocked until the update is resolved, even after
|
|
132
|
+
the timer expires. If no Nexus commands are enabled, a selected name can remain
|
|
133
|
+
saved without an active slash command. Once a custom name is confirmed, replace
|
|
134
|
+
only `/nexus` in examples: a confirmed `coins` root makes `/nexus balance` become
|
|
135
|
+
`/coins balance`, with the same subcommands and access rules.
|
|
136
|
+
|
|
137
|
+
A new reserved command or restricted-list update can select `nexus` again without
|
|
138
|
+
extending the existing expiry or changing unrelated settings. Check the reset's
|
|
139
|
+
pending/confirmed state and reapply Discord command permission overrides after
|
|
140
|
+
the update.
|
|
141
|
+
|
|
142
|
+
Private command-history inspection, timer reset and rename override by Helyx staff
|
|
143
|
+
are a Hosted-only support path. Remote Self-Hosted and Cloud-connected
|
|
144
|
+
installations do not have that recovery capability. Keep custom-name activation
|
|
145
|
+
held when the required policy or recovery path is unavailable; do not bypass it
|
|
146
|
+
by changing stored records or registering a competing command manually. Check
|
|
147
|
+
your release, `npm run doctor`, module health and the current Discord command
|
|
148
|
+
picker, then use the supported [privacy and support routes](privacy-and-support.md)
|
|
149
|
+
without sharing credentials. Hosted dashboard workflow details are in the
|
|
150
|
+
[shared Nexus Settings guide](https://docs.helyx.gg/modules/nexus-rewards/settings/).
|
|
151
|
+
|
|
98
152
|
## Before production use
|
|
99
153
|
|
|
100
154
|
1. Prove `npm run doctor` from the production environment.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helyx/bot",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"description": "Production Discord runtime for the modular Helyx platform.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"helyx",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"type": "module",
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
33
|
+
"command-root-catalogue.json",
|
|
33
34
|
"!dist/*.tsbuildinfo",
|
|
34
35
|
"!dist/**/*.map",
|
|
35
36
|
"documentation",
|
|
@@ -50,31 +51,31 @@
|
|
|
50
51
|
"start": "node --env-file-if-exists=../../.env dist/index.js"
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
53
|
-
"@helyx/config": ">=1.
|
|
54
|
-
"@helyx/control-protocol": ">=0.
|
|
54
|
+
"@helyx/config": ">=1.6.0 <2.0.0",
|
|
55
|
+
"@helyx/control-protocol": ">=0.9.0 <0.10.0",
|
|
55
56
|
"@helyx/core": ">=0.2.0 <0.3.0",
|
|
56
|
-
"@helyx/database": ">=2.
|
|
57
|
-
"@helyx/database-contracts": ">=0.
|
|
57
|
+
"@helyx/database": ">=2.5.0 <3.0.0",
|
|
58
|
+
"@helyx/database-contracts": ">=0.3.0 <0.4.0",
|
|
58
59
|
"@helyx/framework": ">=0.2.0 <0.3.0",
|
|
59
|
-
"@helyx/module-manifest": ">=0.
|
|
60
|
+
"@helyx/module-manifest": ">=0.14.0 <0.15.0",
|
|
60
61
|
"@helyx/platform": ">=1.3.0 <2.0.0",
|
|
61
|
-
"@helyx/sdk": ">=0.
|
|
62
|
+
"@helyx/sdk": ">=0.17.0 <0.18.0",
|
|
62
63
|
"@helyx/status-client": ">=0.3.0 <0.4.0",
|
|
63
|
-
"@helyx/verification-contracts": ">=0.
|
|
64
|
+
"@helyx/verification-contracts": ">=0.5.0 <0.6.0",
|
|
64
65
|
"discord.js": "14.27.0",
|
|
65
66
|
"fastify": "5.12.7",
|
|
66
|
-
"pino": "10.
|
|
67
|
-
"redis": "6.
|
|
68
|
-
"undici": "6.29.
|
|
69
|
-
"ws": "8.
|
|
70
|
-
"zod": "4.
|
|
67
|
+
"pino": "10.4.0",
|
|
68
|
+
"redis": "6.3.0",
|
|
69
|
+
"undici": "6.29.1",
|
|
70
|
+
"ws": "8.22.0",
|
|
71
|
+
"zod": "4.6.7"
|
|
71
72
|
},
|
|
72
73
|
"optionalDependencies": {
|
|
73
|
-
"@helyx/database-mysql": ">=0.
|
|
74
|
+
"@helyx/database-mysql": ">=0.3.0 <0.4.0"
|
|
74
75
|
},
|
|
75
76
|
"peerDependencies": {
|
|
76
|
-
"@helyx/module-automod": ">=1.1.
|
|
77
|
-
"@helyx/module-moderation": ">=1.1.
|
|
77
|
+
"@helyx/module-automod": ">=1.1.2 <2.0.0",
|
|
78
|
+
"@helyx/module-moderation": ">=1.1.2 <2.0.0"
|
|
78
79
|
},
|
|
79
80
|
"peerDependenciesMeta": {
|
|
80
81
|
"@helyx/module-automod": {
|
|
@@ -85,6 +86,6 @@
|
|
|
85
86
|
}
|
|
86
87
|
},
|
|
87
88
|
"devDependencies": {
|
|
88
|
-
"@types/ws": "8.18.
|
|
89
|
+
"@types/ws": "8.18.2"
|
|
89
90
|
}
|
|
90
91
|
}
|