@helyx/bot 0.4.11 → 0.4.13

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.
@@ -1,5 +1,5 @@
1
1
  import { safeError, ServiceRegistry } from "@helyx/core";
2
- import { checkDatabaseConnection, createDatabasePool, InteractionSessionRepository, RateLimitRepository, runConfiguredModuleMigrations, runCoreMigrations, RuntimeStateRepository, } from "@helyx/database";
2
+ import { checkDatabaseConnection, createDatabasePool, AuditRepository, InteractionSessionRepository, RateLimitRepository, runConfiguredModuleMigrations, runCoreMigrations, RuntimeStateRepository, } from "@helyx/database";
3
3
  import { createPlatformServices, PostgresPrivacyProviderExecutor, } from "@helyx/platform";
4
4
  import { HELYX_SERVICE_NAMES } from "@helyx/sdk";
5
5
  import { Client, ShardingManager } from "discord.js";
@@ -29,12 +29,25 @@ import { encodeNumericIpcValue } from "./numeric-write-behind-proxy.js";
29
29
  const LEASE_RENEW_INTERVAL_MS = 10_000;
30
30
  const STARTUP_LEASE_RENEW_INTERVAL_MS = 5_000;
31
31
  const LEASE_VALIDATION_TIMEOUT_MS = 1_000;
32
+ const LEASE_RENEW_ATTEMPTS = 2;
32
33
  const RETENTION_CLEANUP_INTERVAL_MS = 60 * 60 * 1_000;
33
34
  const STARTUP_WAIT_MS = 60_000;
34
35
  const WORKER_SHUTDOWN_MS = 5_000;
35
36
  const MAX_RESTARTS_PER_WINDOW = 5;
36
37
  const RESTART_WINDOW_MS = 10 * 60 * 1_000;
37
- export const STEADY_STATE_LEASE_LOSS_BOUND_MS = LEASE_RENEW_INTERVAL_MS + LEASE_VALIDATION_TIMEOUT_MS + WORKER_SHUTDOWN_MS;
38
+ export const STEADY_STATE_LEASE_LOSS_BOUND_MS = LEASE_RENEW_INTERVAL_MS +
39
+ LEASE_VALIDATION_TIMEOUT_MS * LEASE_RENEW_ATTEMPTS +
40
+ WORKER_SHUTDOWN_MS;
41
+ const FATAL_SUPERVISOR_SHUTDOWN_REASONS = new Set([
42
+ "generation-lease-lost",
43
+ "leader-lease-lost",
44
+ "leader-promotion-failed",
45
+ "rebalance-failed",
46
+ "coordination-unavailable",
47
+ ]);
48
+ export function supervisorShutdownExitCode(reason) {
49
+ return FATAL_SUPERVISOR_SHUTDOWN_REASONS.has(reason) ? 1 : undefined;
50
+ }
38
51
  export function createStartupWorkerHeartbeatMaintenance(heartbeat, intervalMs = STARTUP_LEASE_RENEW_INTERVAL_MS) {
39
52
  const timer = setInterval(heartbeat, intervalMs);
40
53
  timer.unref();
@@ -150,6 +163,11 @@ export async function startShardSupervisor(environment, processEnvironment, work
150
163
  });
151
164
  const commandClient = new Client({ intents: [] });
152
165
  const unavailableResources = {
166
+ listGuildPresence: (guildIds) => Promise.resolve(guildIds.map((guildId) => ({
167
+ guildId,
168
+ state: "unknown",
169
+ observedAt: new Date().toISOString(),
170
+ }))),
153
171
  getGuildResources: () => Promise.reject(new Error("Guild resources are worker-owned")),
154
172
  sendToChannel: () => Promise.reject(new Error("Guild resources are worker-owned")),
155
173
  deleteChannelMessage: () => Promise.reject(new Error("Guild resources are worker-owned")),
@@ -883,8 +901,19 @@ export async function startShardSupervisor(environment, processEnvironment, work
883
901
  try {
884
902
  if (!coordinator)
885
903
  return;
886
- const renewal = await renewSteadyStateOwnership(coordinator, generationDescriptor, deploymentOrder, localLeases, leaderLease).catch(() => undefined);
887
- if (!renewal?.generationRenewed) {
904
+ let renewal;
905
+ try {
906
+ renewal = await retryOwnershipRenewal(() => renewSteadyStateOwnership(coordinator, generationDescriptor, deploymentOrder, localLeases, leaderLease));
907
+ }
908
+ catch (error) {
909
+ logger.error({
910
+ event: "shard.coordination_renewal_failed",
911
+ error: safeError(error),
912
+ }, "Shard coordination could not be renewed after a bounded retry");
913
+ await shutdown("coordination-unavailable");
914
+ return;
915
+ }
916
+ if (!renewal.generationRenewed) {
888
917
  logger.error({ event: "shard.generation_lease_lost" }, "Shard generation lease was lost; stopping this supervisor safely");
889
918
  await shutdown("generation-lease-lost");
890
919
  return;
@@ -1164,6 +1193,9 @@ export async function startShardSupervisor(environment, processEnvironment, work
1164
1193
  if (shuttingDown)
1165
1194
  return;
1166
1195
  shuttingDown = true;
1196
+ const shutdownExitCode = supervisorShutdownExitCode(reason);
1197
+ if (shutdownExitCode !== undefined)
1198
+ process.exitCode = shutdownExitCode;
1167
1199
  if (leaseTimer)
1168
1200
  clearInterval(leaseTimer);
1169
1201
  if (startupLeaseTimer)
@@ -1450,15 +1482,17 @@ function failedCloud(requestId, message) {
1450
1482
  }
1451
1483
  async function cleanupExpiredCoreRecords(database, logger) {
1452
1484
  try {
1453
- const [sessions, rateLimits] = await Promise.all([
1485
+ const [sessions, rateLimits, verificationAudit] = await Promise.all([
1454
1486
  new InteractionSessionRepository(database).deleteExpired(),
1455
1487
  new RateLimitRepository(database).deleteExpired(),
1488
+ new AuditRepository(database).deleteModuleEventsOlderThanMonths("helyx.verification", 12),
1456
1489
  ]);
1457
- if (sessions > 0 || rateLimits > 0)
1490
+ if (sessions > 0 || rateLimits > 0 || verificationAudit > 0)
1458
1491
  logger.info({
1459
1492
  event: "database.retention_cleaned",
1460
1493
  interactionSessions: sessions,
1461
1494
  rateLimitWindows: rateLimits,
1495
+ verificationAuditEvents: verificationAudit,
1462
1496
  }, "Expired core records removed");
1463
1497
  }
1464
1498
  catch (error) {
@@ -1509,6 +1543,20 @@ async function waitForDeath(shard, timeoutMs) {
1509
1543
  export function boundedRespawnDelay(attempt) {
1510
1544
  return Math.min(30_000, 1_000 * 2 ** Math.max(0, attempt - 1));
1511
1545
  }
1546
+ export async function retryOwnershipRenewal(renew, attempts = LEASE_RENEW_ATTEMPTS) {
1547
+ if (!Number.isSafeInteger(attempts) || attempts < 1)
1548
+ throw new Error("Ownership renewal attempts must be a positive integer");
1549
+ let lastError;
1550
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
1551
+ try {
1552
+ return await renew();
1553
+ }
1554
+ catch (error) {
1555
+ lastError = error;
1556
+ }
1557
+ }
1558
+ throw lastError;
1559
+ }
1512
1560
  export function selectFencedShardHealth(input) {
1513
1561
  const liveByShard = new Map(input.liveLeases.map((lease) => [lease.shardId, lease]));
1514
1562
  const selected = new Map();
@@ -35,7 +35,10 @@ export declare function parseShardWorkerEnvironment(processEnvironment: NodeJS.P
35
35
  HELYX_MODULE_PACKAGES: string[];
36
36
  HELYX_MODULE_CATALOGUE_PATH: string | undefined;
37
37
  HELYX_SUPPORT_STAFF_CHANNEL_ID: string | undefined;
38
+ HELYX_VERIFICATION_ENABLED: boolean;
39
+ HELYX_VERIFICATION_SIGNING_PUBLIC_KEYS: Record<string, string>;
38
40
  HELYX_BOT_CONTROL_PORT: number;
41
+ HELYX_GUILD_RECONCILIATION_INTERVAL_SECONDS: number;
39
42
  HELYX_PRIVACY_ENABLED: boolean;
40
43
  HELYX_MESSAGE_CONTENT_ENABLED: boolean;
41
44
  HELYX_CLOUD_CONNECT_ENABLED: boolean;
@@ -52,6 +55,9 @@ export declare function parseShardWorkerEnvironment(processEnvironment: NodeJS.P
52
55
  NODE_ENV: "development" | "test" | "production";
53
56
  HELYX_BOT_CONTROL_SECRET?: string | undefined;
54
57
  HELYX_SUPPORT_SERVICE_SECRET?: string | undefined;
58
+ HELYX_VERIFICATION_INSTALLATION_ID?: string | undefined;
59
+ HELYX_VERIFICATION_URL?: string | undefined;
60
+ HELYX_VERIFICATION_INTERNAL_SECRET?: string | undefined;
55
61
  HELYX_PRIVACY_EXECUTOR_DATABASE_URL?: string | undefined;
56
62
  HELYX_PRIVACY_EXECUTOR_DATABASE_SSL?: "disable" | "require" | "verify-full" | undefined;
57
63
  HELYX_SHARD_COUNT?: number | undefined;
@@ -1,6 +1,6 @@
1
1
  import { parseBotEnvironment } from "@helyx/config";
2
2
  import { safeError, ServiceRegistry } from "@helyx/core";
3
- import { checkDatabaseConnection, createDatabasePool } from "@helyx/database";
3
+ import { checkDatabaseConnection, createDatabasePool, VerificationSetupRepository, } from "@helyx/database";
4
4
  import { createRuntime } from "@helyx/framework";
5
5
  import { createPlatformServices } from "@helyx/platform";
6
6
  import { HELYX_SERVICE_NAMES } from "@helyx/sdk";
@@ -9,8 +9,9 @@ import { monitorEventLoopDelay } from "node:perf_hooks";
9
9
  import pino from "pino";
10
10
  import { createCloudManagementModule } from "./cloud-management-module.js";
11
11
  import { BotControlService, DiscordGuildResourceDirectory, mapBotControlError, } from "./control-server.js";
12
- import { DiscordMessageDeliveryService, DiscordMemberEnforcementService, DiscordMemberRoleService, DiscordResourceInspection, installModuleEvents, } from "./events.js";
13
- import { installGuildTracking } from "./guilds.js";
12
+ import { HttpVerificationService } from "./verification-service.js";
13
+ import { DiscordMessageDeliveryService, DiscordMemberEnforcementService, DiscordMemberRoleService, DiscordVerificationMemberService, DiscordVerificationSetupService, DiscordResourceInspection, installModuleEvents, } from "./events.js";
14
+ import { installGuildTracking, } from "./guilds.js";
14
15
  import { installInteractions, } from "./interactions.js";
15
16
  import { createManagementModule } from "./management-module.js";
16
17
  import { DiscordModuleLoggingService } from "./module-logging.js";
@@ -73,6 +74,7 @@ export async function startShardWorker(processEnvironment = process.env, identit
73
74
  let shuttingDown = false;
74
75
  let runtimeStarted = false;
75
76
  let moduleEvents;
77
+ let guildTracking;
76
78
  let client;
77
79
  let cloudProxy;
78
80
  let shardHealthProvider;
@@ -84,6 +86,7 @@ export async function startShardWorker(processEnvironment = process.env, identit
84
86
  return;
85
87
  shuttingDown = true;
86
88
  status = "stopped";
89
+ guildTracking?.stop();
87
90
  if (healthTimer)
88
91
  clearInterval(healthTimer);
89
92
  leaseHeartbeatWatchdog?.stop();
@@ -168,6 +171,18 @@ export async function startShardWorker(processEnvironment = process.env, identit
168
171
  })) {
169
172
  platformServices.set(name, service);
170
173
  }
174
+ if (environment.HELYX_VERIFICATION_ENABLED &&
175
+ environment.HELYX_VERIFICATION_URL &&
176
+ environment.HELYX_VERIFICATION_INTERNAL_SECRET) {
177
+ platformServices.set(HELYX_SERVICE_NAMES.verification, new HttpVerificationService({
178
+ baseUrl: environment.HELYX_VERIFICATION_URL,
179
+ internalSecret: environment.HELYX_VERIFICATION_INTERNAL_SECRET,
180
+ installationId: environment.HELYX_VERIFICATION_INSTALLATION_ID,
181
+ publicSigningKeys: environment.HELYX_VERIFICATION_SIGNING_PUBLIC_KEYS,
182
+ }));
183
+ platformServices.set(HELYX_SERVICE_NAMES.verificationMembers, new DiscordVerificationMemberService(client));
184
+ platformServices.set(HELYX_SERVICE_NAMES.verificationSetup, new DiscordVerificationSetupService(client, new VerificationSetupRepository(database)));
185
+ }
171
186
  numericWriteBehind =
172
187
  identity.scope === "coordinated"
173
188
  ? new WorkerNumericWriteBehindProxy(identity)
@@ -194,13 +209,15 @@ export async function startShardWorker(processEnvironment = process.env, identit
194
209
  ...(cloudManagementModule ? [cloudManagementModule.manifest.id] : []),
195
210
  ...(operationsModule ? [operationsModule.manifest.id] : []),
196
211
  ]);
197
- const prepareGuilds = installGuildTracking(client, database, publicModules, logger, enabledCoreModuleIds, {
212
+ const shardOwnership = {
198
213
  generation: identity.generation,
199
214
  deploymentOrder: BigInt(identity.deploymentOrder ?? 1),
200
215
  supervisorId: identity.supervisorId,
201
216
  shardId: identity.shardId,
202
217
  fencingToken: BigInt(identity.fencingToken),
203
- });
218
+ };
219
+ const prepareGuilds = installGuildTracking(client, database, publicModules, logger, enabledCoreModuleIds, shardOwnership, environment.HELYX_GUILD_RECONCILIATION_INTERVAL_SECONDS * 1_000, identity.shardCount);
220
+ guildTracking = prepareGuilds;
204
221
  const commandsReady = installInteractions(client, modules, shardWorkerCommandRegistration({
205
222
  ...(environment.DISCORD_INTERNAL_GUILD_ID
206
223
  ? { internalGuildId: environment.DISCORD_INTERNAL_GUILD_ID }
@@ -218,7 +235,7 @@ export async function startShardWorker(processEnvironment = process.env, identit
218
235
  guildId: environment.DISCORD_INTERNAL_GUILD_ID,
219
236
  channelId: environment.HELYX_SUPPORT_STAFF_CHANNEL_ID,
220
237
  }
221
- : undefined);
238
+ : undefined, shardOwnership, identity.shardCount);
222
239
  process.on("message", (raw) => {
223
240
  const parsed = supervisorMessageSchema.safeParse(raw);
224
241
  if (!parsed.success)
@@ -0,0 +1,50 @@
1
+ import { type ActionReceipt, type ChallengeCreateRequest, type ChallengeCreateResponse, type DashboardAttemptPage, type DashboardAttemptQuery, type DashboardSafeAttemptDetail, type SignedAssessmentEnvelope, type VerificationInstallationClaims } from "@helyx/verification-contracts";
2
+ import type { VerificationResultContext, VerificationService } from "@helyx/sdk";
3
+ export interface HttpVerificationServiceOptions {
4
+ baseUrl: string;
5
+ internalSecret: string;
6
+ publicSigningKeys: Readonly<Record<string, string>>;
7
+ installationId: string;
8
+ fetchImplementation?: typeof fetch;
9
+ }
10
+ export declare class HttpVerificationService implements VerificationService {
11
+ #private;
12
+ constructor(options: HttpVerificationServiceOptions);
13
+ getInstallationContext(): Promise<{
14
+ installation: VerificationInstallationClaims;
15
+ }>;
16
+ createChallenge(input: ChallengeCreateRequest): Promise<ChallengeCreateResponse>;
17
+ cancelChallenges(input: {
18
+ installationId: string;
19
+ guildId: string;
20
+ userId?: string;
21
+ }): Promise<{
22
+ cancelled: number;
23
+ }>;
24
+ listAttempts(input: DashboardAttemptQuery & {
25
+ installationId: string;
26
+ guildId: string;
27
+ }): Promise<DashboardAttemptPage>;
28
+ getAttempt(input: {
29
+ installationId: string;
30
+ guildId: string;
31
+ attemptId: string;
32
+ }): Promise<DashboardSafeAttemptDetail | null>;
33
+ decideAttempt(input: {
34
+ installationId: string;
35
+ guildId: string;
36
+ attemptId: string;
37
+ expectedVersion: number;
38
+ actorUserId: string;
39
+ decision: "approve" | "deny" | "retry" | "dismiss_relationship";
40
+ reason: string;
41
+ }): Promise<DashboardSafeAttemptDetail>;
42
+ registerResultHandler(handler: (context: VerificationResultContext) => Promise<ActionReceipt>): () => void;
43
+ acceptResult(envelopeInput: SignedAssessmentEnvelope): Promise<ActionReceipt>;
44
+ health(): Promise<{
45
+ status: "healthy" | "degraded" | "unavailable";
46
+ provider: "available" | "degraded" | "unavailable";
47
+ allowanceRemaining?: number;
48
+ }>;
49
+ }
50
+ //# sourceMappingURL=verification-service.d.ts.map
@@ -0,0 +1,166 @@
1
+ import { actionReceiptSchema, challengeCreateRequestSchema, challengeCreateResponseSchema, dashboardAttemptPageSchema, dashboardAttemptQuerySchema, dashboardSafeAttemptDetailSchema, signedAssessmentEnvelopeSchema, } from "@helyx/verification-contracts";
2
+ import { createPublicKey, verify } from "node:crypto";
3
+ import { z } from "zod";
4
+ const healthSchema = z
5
+ .object({
6
+ status: z.enum(["healthy", "degraded", "unavailable"]),
7
+ provider: z.enum(["available", "degraded", "unavailable"]),
8
+ allowanceRemaining: z.number().int().min(0).optional(),
9
+ })
10
+ .strict();
11
+ export class HttpVerificationService {
12
+ #baseUrl;
13
+ #internalSecret;
14
+ #publicSigningKeys;
15
+ #fetch;
16
+ #installationId;
17
+ #handler;
18
+ constructor(options) {
19
+ this.#baseUrl = options.baseUrl.replace(/\/$/u, "");
20
+ this.#internalSecret = options.internalSecret;
21
+ this.#publicSigningKeys = { ...options.publicSigningKeys };
22
+ this.#installationId = options.installationId;
23
+ this.#fetch = options.fetchImplementation ?? fetch;
24
+ }
25
+ getInstallationContext() {
26
+ return Promise.resolve({
27
+ installation: {
28
+ installationId: this.#installationId,
29
+ sourceClassification: "helyx_hosted",
30
+ capabilities: [
31
+ "challenge.create.v1",
32
+ "assessment.receive.v1",
33
+ "action.receipt.write.v1",
34
+ "member.current.read.v1",
35
+ "ban.current.read.v1",
36
+ "service.health.read.v1",
37
+ ],
38
+ authenticatedAt: new Date().toISOString(),
39
+ },
40
+ });
41
+ }
42
+ async createChallenge(input) {
43
+ return challengeCreateResponseSchema.parse(await this.#request("/internal/v1/challenges", {
44
+ method: "POST",
45
+ body: challengeCreateRequestSchema.parse(input),
46
+ }));
47
+ }
48
+ async cancelChallenges(input) {
49
+ return z
50
+ .object({ cancelled: z.number().int().min(0) })
51
+ .strict()
52
+ .parse(await this.#request("/internal/v1/challenges/cancel", {
53
+ method: "POST",
54
+ body: input,
55
+ }));
56
+ }
57
+ async listAttempts(input) {
58
+ const { installationId, guildId, ...query } = input;
59
+ return dashboardAttemptPageSchema.parse(await this.#request("/internal/v1/attempts/query", {
60
+ method: "POST",
61
+ body: {
62
+ installationId,
63
+ guildId,
64
+ query: dashboardAttemptQuerySchema.parse(query),
65
+ },
66
+ }));
67
+ }
68
+ async getAttempt(input) {
69
+ const result = await this.#request(`/internal/v1/attempts/${encodeURIComponent(input.attemptId)}`, { method: "POST", body: input }, true);
70
+ return result === null
71
+ ? null
72
+ : dashboardSafeAttemptDetailSchema.parse(result);
73
+ }
74
+ async decideAttempt(input) {
75
+ return dashboardSafeAttemptDetailSchema.parse(await this.#request(`/internal/v1/attempts/${encodeURIComponent(input.attemptId)}/decision`, { method: "POST", body: input }));
76
+ }
77
+ registerResultHandler(handler) {
78
+ if (this.#handler !== undefined)
79
+ throw new Error("Verification result handler is already registered");
80
+ this.#handler = handler;
81
+ return () => {
82
+ if (this.#handler === handler)
83
+ this.#handler = undefined;
84
+ };
85
+ }
86
+ async acceptResult(envelopeInput) {
87
+ const envelope = signedAssessmentEnvelopeSchema.parse(envelopeInput);
88
+ if (envelope.payload.deploymentSourceClassification !== "helyx_hosted" ||
89
+ envelope.payload.evidenceSources.discord !== "helyx_hosted" ||
90
+ envelope.payload.installationId !== this.#installationId)
91
+ throw new Error("Verification assessment is not bound to hosted Helyx");
92
+ const publicKey = this.#publicSigningKeys[envelope.keyId];
93
+ if (publicKey === undefined || !verifyEnvelope(envelope, publicKey))
94
+ throw new Error("Verification assessment signature is invalid");
95
+ const expiresAt = Date.parse(envelope.payload.expiresAt);
96
+ const issuedAt = Date.parse(envelope.payload.issuedAt);
97
+ const now = Date.now();
98
+ if (issuedAt > now + 30_000 || expiresAt <= now)
99
+ throw new Error("Verification assessment is outside its valid lifetime");
100
+ if (!this.#handler)
101
+ throw new Error("Verification result handler is unavailable");
102
+ return actionReceiptSchema.parse(await this.#handler({
103
+ envelope,
104
+ correlationId: envelope.payload.attemptId,
105
+ }));
106
+ }
107
+ async health() {
108
+ const health = healthSchema.parse(await this.#request("/internal/v1/health", { method: "GET" }));
109
+ return {
110
+ status: health.status,
111
+ provider: health.provider,
112
+ ...(health.allowanceRemaining === undefined
113
+ ? {}
114
+ : { allowanceRemaining: health.allowanceRemaining }),
115
+ };
116
+ }
117
+ async #request(path, input, allowNotFound = false) {
118
+ let response;
119
+ try {
120
+ response = await this.#fetch(`${this.#baseUrl}${path}`, {
121
+ method: input.method,
122
+ headers: {
123
+ authorization: `Bearer ${this.#internalSecret}`,
124
+ accept: "application/json",
125
+ ...(input.body === undefined
126
+ ? {}
127
+ : { "content-type": "application/json" }),
128
+ },
129
+ ...(input.body === undefined
130
+ ? {}
131
+ : { body: JSON.stringify(input.body) }),
132
+ signal: AbortSignal.timeout(10_000),
133
+ });
134
+ }
135
+ catch {
136
+ throw new Error("Verification service is unavailable");
137
+ }
138
+ if (allowNotFound && response.status === 404)
139
+ return null;
140
+ if (!response.ok)
141
+ throw new Error(response.status >= 500
142
+ ? "Verification service is unavailable"
143
+ : "Verification request was rejected");
144
+ try {
145
+ return await response.json();
146
+ }
147
+ catch {
148
+ throw new Error("Verification service returned an invalid response");
149
+ }
150
+ }
151
+ }
152
+ function verifyEnvelope(envelope, publicKeySpkiBase64) {
153
+ let key;
154
+ try {
155
+ key = createPublicKey({
156
+ key: Buffer.from(publicKeySpkiBase64, "base64"),
157
+ format: "der",
158
+ type: "spki",
159
+ });
160
+ }
161
+ catch {
162
+ return false;
163
+ }
164
+ return verify(null, Buffer.from(JSON.stringify(envelope.payload), "utf8"), key, Buffer.from(envelope.signature, "base64url"));
165
+ }
166
+ //# sourceMappingURL=verification-service.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helyx/bot",
3
- "version": "0.4.11",
3
+ "version": "0.4.13",
4
4
  "description": "Production Discord runtime for the modular Helyx platform.",
5
5
  "keywords": [
6
6
  "helyx",
@@ -48,14 +48,15 @@
48
48
  "start": "node --env-file-if-exists=../../.env dist/index.js"
49
49
  },
50
50
  "dependencies": {
51
- "@helyx/config": "0.4.3",
52
- "@helyx/control-protocol": "0.2.0",
53
- "@helyx/core": "0.1.11",
54
- "@helyx/database": "0.5.0",
55
- "@helyx/framework": "0.1.11",
56
- "@helyx/module-manifest": "0.5.0",
57
- "@helyx/platform": "0.3.0",
58
- "@helyx/sdk": "0.4.0",
51
+ "@helyx/config": "0.5.0",
52
+ "@helyx/control-protocol": "0.3.0",
53
+ "@helyx/core": "0.1.12",
54
+ "@helyx/database": "0.6.0",
55
+ "@helyx/framework": "0.1.12",
56
+ "@helyx/module-manifest": "0.6.0",
57
+ "@helyx/platform": "0.4.0",
58
+ "@helyx/sdk": "0.5.0",
59
+ "@helyx/verification-contracts": "0.1.0",
59
60
  "discord.js": "14.27.0",
60
61
  "fastify": "5.10.0",
61
62
  "pino": "10.3.1",