@wopr-network/platform-core 1.31.0 → 1.32.0

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.
@@ -87,13 +87,13 @@ export class DrizzleCryptoChargeRepository {
87
87
  status: "New",
88
88
  chain: input.chain,
89
89
  token: input.token,
90
- depositAddress: input.depositAddress,
90
+ depositAddress: input.depositAddress.toLowerCase(),
91
91
  derivationIndex: input.derivationIndex,
92
92
  });
93
93
  }
94
94
  /** Look up a charge by its deposit address. */
95
95
  async getByDepositAddress(address) {
96
- const row = (await this.db.select().from(cryptoCharges).where(eq(cryptoCharges.depositAddress, address)))[0];
96
+ const row = (await this.db.select().from(cryptoCharges).where(eq(cryptoCharges.depositAddress, address.toLowerCase())))[0];
97
97
  if (!row)
98
98
  return null;
99
99
  return this.toRecord(row);
@@ -26,7 +26,7 @@ export declare class ContainerUpdater {
26
26
  constructor(docker: Docker, store: IProfileStore, fleet: FleetManager, _poller: ImagePoller);
27
27
  /**
28
28
  * Update a bot's container to the latest image available for its release channel.
29
- * Rolls back if the new container fails health checks within 60s.
29
+ * Rolls back if the new container fails health checks within 120s.
30
30
  */
31
31
  updateBot(botId: string): Promise<UpdateResult>;
32
32
  private doUpdateBot;
@@ -1,7 +1,7 @@
1
1
  import { logger } from "../config/logger.js";
2
2
  import { getContainerDigest } from "./image-poller.js";
3
3
  /** How long to wait for a container to become healthy after update (ms) */
4
- const HEALTH_CHECK_TIMEOUT_MS = 60_000;
4
+ const HEALTH_CHECK_TIMEOUT_MS = 120_000;
5
5
  /** How often to check container health during update verification (ms) */
6
6
  const HEALTH_CHECK_POLL_MS = 5_000;
7
7
  /**
@@ -22,7 +22,7 @@ export class ContainerUpdater {
22
22
  }
23
23
  /**
24
24
  * Update a bot's container to the latest image available for its release channel.
25
- * Rolls back if the new container fails health checks within 60s.
25
+ * Rolls back if the new container fails health checks within 120s.
26
26
  */
27
27
  async updateBot(botId) {
28
28
  if (this.updating.has(botId)) {
@@ -246,8 +246,8 @@ describe("ContainerUpdater", () => {
246
246
  docker.listContainers.mockResolvedValue([{ Id: "container-123" }]);
247
247
  docker.getContainer.mockReturnValue(startingContainer);
248
248
  const promise = updater.updateBot("bot-1");
249
- // Advance past the 60s timeout (60_000ms) in increments of poll interval (5_000ms)
250
- for (let i = 0; i < 13; i++) {
249
+ // Advance past the 120s timeout (120_000ms) in increments of poll interval (5_000ms)
250
+ for (let i = 0; i < 25; i++) {
251
251
  await vi.advanceTimersByTimeAsync(5_000);
252
252
  }
253
253
  const result = await promise;
@@ -44,14 +44,14 @@ export type CommandType = (typeof ALLOWED_COMMANDS)[number];
44
44
  export declare const commandSchema: z.ZodObject<{
45
45
  id: z.ZodString;
46
46
  type: z.ZodEnum<{
47
+ "bot.logs": "bot.logs";
47
48
  "bot.start": "bot.start";
48
49
  "bot.stop": "bot.stop";
49
50
  "bot.restart": "bot.restart";
51
+ "bot.remove": "bot.remove";
50
52
  "bot.update": "bot.update";
51
53
  "bot.export": "bot.export";
52
54
  "bot.import": "bot.import";
53
- "bot.remove": "bot.remove";
54
- "bot.logs": "bot.logs";
55
55
  "bot.inspect": "bot.inspect";
56
56
  "backup.upload": "backup.upload";
57
57
  "backup.download": "backup.download";
@@ -0,0 +1,41 @@
1
+ import type { RolloutOrchestrator } from "../fleet/rollout-orchestrator.js";
2
+ import type { ITenantUpdateConfigRepository } from "../fleet/tenant-update-config-repository.js";
3
+ export declare function createAdminFleetUpdateRouter(getOrchestrator: () => RolloutOrchestrator, getConfigRepo: () => ITenantUpdateConfigRepository): import("@trpc/server").TRPCBuiltRouter<{
4
+ ctx: import("./init.js").TRPCContext;
5
+ meta: object;
6
+ errorShape: import("@trpc/server").TRPCDefaultErrorShape;
7
+ transformer: false;
8
+ }, import("@trpc/server").TRPCDecorateCreateRouterOptions<{
9
+ /** Get current rollout status */
10
+ rolloutStatus: import("@trpc/server").TRPCQueryProcedure<{
11
+ input: void;
12
+ output: {
13
+ isRolling: boolean;
14
+ };
15
+ meta: object;
16
+ }>;
17
+ /** Force trigger a rollout for all auto-update tenants */
18
+ forceRollout: import("@trpc/server").TRPCMutationProcedure<{
19
+ input: void;
20
+ output: {
21
+ triggered: boolean;
22
+ };
23
+ meta: object;
24
+ }>;
25
+ /** List all tenant update configs */
26
+ listTenantConfigs: import("@trpc/server").TRPCQueryProcedure<{
27
+ input: void;
28
+ output: import("../fleet/tenant-update-config-repository.js").TenantUpdateConfig[];
29
+ meta: object;
30
+ }>;
31
+ /** Override a specific tenant's update config */
32
+ setTenantConfig: import("@trpc/server").TRPCMutationProcedure<{
33
+ input: {
34
+ tenantId: string;
35
+ mode: "manual" | "auto";
36
+ preferredHourUtc?: number | undefined;
37
+ };
38
+ output: void;
39
+ meta: object;
40
+ }>;
41
+ }>>;
@@ -0,0 +1,51 @@
1
+ import { z } from "zod";
2
+ import { logger } from "../config/logger.js";
3
+ import { adminProcedure, router } from "./init.js";
4
+ export function createAdminFleetUpdateRouter(getOrchestrator, getConfigRepo) {
5
+ return router({
6
+ /** Get current rollout status */
7
+ rolloutStatus: adminProcedure.query(() => {
8
+ const orchestrator = getOrchestrator();
9
+ return {
10
+ isRolling: orchestrator.isRolling,
11
+ };
12
+ }),
13
+ /** Force trigger a rollout for all auto-update tenants */
14
+ forceRollout: adminProcedure.mutation(async () => {
15
+ const orchestrator = getOrchestrator();
16
+ logger.info("Admin: fleet.forceRollout");
17
+ // Fire and forget — don't block the admin request
18
+ orchestrator.rollout().catch((err) => {
19
+ logger.error("Force rollout failed", {
20
+ error: err.message,
21
+ });
22
+ });
23
+ return { triggered: true };
24
+ }),
25
+ /** List all tenant update configs */
26
+ listTenantConfigs: adminProcedure.query(async () => {
27
+ const repo = getConfigRepo();
28
+ return repo.listAutoEnabled();
29
+ }),
30
+ /** Override a specific tenant's update config */
31
+ setTenantConfig: adminProcedure
32
+ .input(z.object({
33
+ tenantId: z.string().min(1),
34
+ mode: z.enum(["auto", "manual"]),
35
+ preferredHourUtc: z.number().int().min(0).max(23).optional(),
36
+ }))
37
+ .mutation(async ({ input }) => {
38
+ const repo = getConfigRepo();
39
+ const existing = await repo.get(input.tenantId);
40
+ await repo.upsert(input.tenantId, {
41
+ mode: input.mode,
42
+ preferredHourUtc: input.preferredHourUtc ?? existing?.preferredHourUtc ?? 3,
43
+ });
44
+ logger.info("Admin: fleet.setTenantConfig", {
45
+ tenantId: input.tenantId,
46
+ mode: input.mode,
47
+ preferredHourUtc: input.preferredHourUtc ?? existing?.preferredHourUtc ?? 3,
48
+ });
49
+ }),
50
+ });
51
+ }
@@ -1,3 +1,4 @@
1
+ export { createAdminFleetUpdateRouter } from "./admin-fleet-update-router.js";
1
2
  export { createFleetUpdateConfigRouter } from "./fleet-update-config-router.js";
2
3
  export { adminProcedure, createCallerFactory, orgAdminProcedure, orgMemberProcedure, protectedProcedure, publicProcedure, router, setTrpcOrgMemberRepo, type TRPCContext, tenantProcedure, } from "./init.js";
3
4
  export { createNotificationTemplateRouter } from "./notification-template-router.js";
@@ -1,3 +1,4 @@
1
+ export { createAdminFleetUpdateRouter } from "./admin-fleet-update-router.js";
1
2
  export { createFleetUpdateConfigRouter } from "./fleet-update-config-router.js";
2
3
  export { adminProcedure, createCallerFactory, orgAdminProcedure, orgMemberProcedure, protectedProcedure, publicProcedure, router, setTrpcOrgMemberRepo, tenantProcedure, } from "./init.js";
3
4
  export { createNotificationTemplateRouter } from "./notification-template-router.js";
@@ -5,14 +5,14 @@
5
5
  {
6
6
  "idx": 0,
7
7
  "version": "7",
8
- "when": 1773159905670,
8
+ "when": 1741622400000,
9
9
  "tag": "0000_slippery_mandrill",
10
10
  "breakpoints": true
11
11
  },
12
12
  {
13
13
  "idx": 1,
14
14
  "version": "7",
15
- "when": 1773279600000,
15
+ "when": 1741708800000,
16
16
  "tag": "0001_infrastructure_extraction",
17
17
  "breakpoints": true
18
18
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-core",
3
- "version": "1.31.0",
3
+ "version": "1.32.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -144,14 +144,16 @@ export class DrizzleCryptoChargeRepository implements ICryptoChargeRepository {
144
144
  status: "New",
145
145
  chain: input.chain,
146
146
  token: input.token,
147
- depositAddress: input.depositAddress,
147
+ depositAddress: input.depositAddress.toLowerCase(),
148
148
  derivationIndex: input.derivationIndex,
149
149
  });
150
150
  }
151
151
 
152
152
  /** Look up a charge by its deposit address. */
153
153
  async getByDepositAddress(address: string): Promise<CryptoChargeRecord | null> {
154
- const row = (await this.db.select().from(cryptoCharges).where(eq(cryptoCharges.depositAddress, address)))[0];
154
+ const row = (
155
+ await this.db.select().from(cryptoCharges).where(eq(cryptoCharges.depositAddress, address.toLowerCase()))
156
+ )[0];
155
157
  if (!row) return null;
156
158
  return this.toRecord(row);
157
159
  }
@@ -287,8 +287,8 @@ describe("ContainerUpdater", () => {
287
287
  docker.getContainer.mockReturnValue(startingContainer);
288
288
 
289
289
  const promise = updater.updateBot("bot-1");
290
- // Advance past the 60s timeout (60_000ms) in increments of poll interval (5_000ms)
291
- for (let i = 0; i < 13; i++) {
290
+ // Advance past the 120s timeout (120_000ms) in increments of poll interval (5_000ms)
291
+ for (let i = 0; i < 25; i++) {
292
292
  await vi.advanceTimersByTimeAsync(5_000);
293
293
  }
294
294
  const result = await promise;
@@ -6,7 +6,7 @@ import { getContainerDigest } from "./image-poller.js";
6
6
  import type { IProfileStore } from "./profile-store.js";
7
7
 
8
8
  /** How long to wait for a container to become healthy after update (ms) */
9
- const HEALTH_CHECK_TIMEOUT_MS = 60_000;
9
+ const HEALTH_CHECK_TIMEOUT_MS = 120_000;
10
10
  /** How often to check container health during update verification (ms) */
11
11
  const HEALTH_CHECK_POLL_MS = 5_000;
12
12
 
@@ -41,7 +41,7 @@ export class ContainerUpdater {
41
41
 
42
42
  /**
43
43
  * Update a bot's container to the latest image available for its release channel.
44
- * Rolls back if the new container fails health checks within 60s.
44
+ * Rolls back if the new container fails health checks within 120s.
45
45
  */
46
46
  async updateBot(botId: string): Promise<UpdateResult> {
47
47
  if (this.updating.has(botId)) {
@@ -0,0 +1,62 @@
1
+ import { z } from "zod";
2
+ import { logger } from "../config/logger.js";
3
+ import type { RolloutOrchestrator } from "../fleet/rollout-orchestrator.js";
4
+ import type { ITenantUpdateConfigRepository } from "../fleet/tenant-update-config-repository.js";
5
+ import { adminProcedure, router } from "./init.js";
6
+
7
+ export function createAdminFleetUpdateRouter(
8
+ getOrchestrator: () => RolloutOrchestrator,
9
+ getConfigRepo: () => ITenantUpdateConfigRepository,
10
+ ) {
11
+ return router({
12
+ /** Get current rollout status */
13
+ rolloutStatus: adminProcedure.query(() => {
14
+ const orchestrator = getOrchestrator();
15
+ return {
16
+ isRolling: orchestrator.isRolling,
17
+ };
18
+ }),
19
+
20
+ /** Force trigger a rollout for all auto-update tenants */
21
+ forceRollout: adminProcedure.mutation(async () => {
22
+ const orchestrator = getOrchestrator();
23
+ logger.info("Admin: fleet.forceRollout");
24
+ // Fire and forget — don't block the admin request
25
+ orchestrator.rollout().catch((err: unknown) => {
26
+ logger.error("Force rollout failed", {
27
+ error: (err as Error).message,
28
+ });
29
+ });
30
+ return { triggered: true };
31
+ }),
32
+
33
+ /** List all tenant update configs */
34
+ listTenantConfigs: adminProcedure.query(async () => {
35
+ const repo = getConfigRepo();
36
+ return repo.listAutoEnabled();
37
+ }),
38
+
39
+ /** Override a specific tenant's update config */
40
+ setTenantConfig: adminProcedure
41
+ .input(
42
+ z.object({
43
+ tenantId: z.string().min(1),
44
+ mode: z.enum(["auto", "manual"]),
45
+ preferredHourUtc: z.number().int().min(0).max(23).optional(),
46
+ }),
47
+ )
48
+ .mutation(async ({ input }) => {
49
+ const repo = getConfigRepo();
50
+ const existing = await repo.get(input.tenantId);
51
+ await repo.upsert(input.tenantId, {
52
+ mode: input.mode,
53
+ preferredHourUtc: input.preferredHourUtc ?? existing?.preferredHourUtc ?? 3,
54
+ });
55
+ logger.info("Admin: fleet.setTenantConfig", {
56
+ tenantId: input.tenantId,
57
+ mode: input.mode,
58
+ preferredHourUtc: input.preferredHourUtc ?? existing?.preferredHourUtc ?? 3,
59
+ });
60
+ }),
61
+ });
62
+ }
package/src/trpc/index.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { createAdminFleetUpdateRouter } from "./admin-fleet-update-router.js";
1
2
  export { createFleetUpdateConfigRouter } from "./fleet-update-config-router.js";
2
3
  export {
3
4
  adminProcedure,