@hogsend/engine 0.25.0 → 0.26.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hogsend/engine",
3
- "version": "0.25.0",
3
+ "version": "0.26.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,14 +40,14 @@
40
40
  "svix": "^1.95.1",
41
41
  "winston": "^3.19.0",
42
42
  "zod": "^4.4.3",
43
- "@hogsend/core": "^0.25.0",
44
- "@hogsend/db": "^0.25.0",
45
- "@hogsend/email": "^0.25.0",
46
- "@hogsend/plugin-posthog": "^0.25.0",
47
- "@hogsend/plugin-resend": "^0.25.0"
43
+ "@hogsend/db": "^0.26.0",
44
+ "@hogsend/core": "^0.26.0",
45
+ "@hogsend/email": "^0.26.0",
46
+ "@hogsend/plugin-posthog": "^0.26.0",
47
+ "@hogsend/plugin-resend": "^0.26.0"
48
48
  },
49
49
  "optionalDependencies": {
50
- "@hogsend/plugin-postmark": "^0.25.0"
50
+ "@hogsend/plugin-postmark": "^0.26.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/node": "^22.15.3",
@@ -72,6 +72,9 @@ export interface ConnectorRuntimesHandle {
72
72
  const LEASE_TTL_MS = 30_000;
73
73
  const RENEW_MS = 10_000;
74
74
  const ELECT_MS = 5_000;
75
+ // ~30s of failed elections (6 * ELECT_MS) before warning loudly that a
76
+ // configured runtime still can't acquire its lease (Redis down or contended).
77
+ const LEASE_MISS_WARN_AT = 6;
75
78
 
76
79
  /** Build the in-process dispatch→transform→ingest sink for one connector. */
77
80
  function makeIngest(client: HogsendClient, connector: DefinedConnector) {
@@ -150,6 +153,7 @@ function startController(
150
153
  let leading = false;
151
154
  let token = "";
152
155
  let stopped = false;
156
+ let leaseMisses = 0;
153
157
  let timer: ReturnType<typeof setTimeout> | undefined;
154
158
 
155
159
  /** Drop leadership: heartbeat key deleted FIRST (immediate Offline), then socket. */
@@ -178,6 +182,7 @@ function startController(
178
182
  ttlMs: LEASE_TTL_MS,
179
183
  });
180
184
  if (won) {
185
+ leaseMisses = 0;
181
186
  leading = true;
182
187
  heartbeat = startConnectorHeartbeat(connectorId, logger);
183
188
  heartbeat.state.setMetadata(rt.getMetadata());
@@ -198,6 +203,22 @@ function startController(
198
203
  leading = false;
199
204
  await releaseLeaderLease({ key: leaseKey, token });
200
205
  }
206
+ } else {
207
+ // Lease not acquired: another replica holds it (benign, normal during
208
+ // rollout) OR Redis is unreachable (the gateway can NEVER connect) —
209
+ // indistinguishable from the boolean. Warn LOUDLY once after ~30s of
210
+ // misses so a genuinely stuck runtime surfaces instead of silently
211
+ // never connecting (which Studio otherwise mis-reads as "intents off").
212
+ leaseMisses++;
213
+ if (leaseMisses === LEASE_MISS_WARN_AT) {
214
+ logger.error(
215
+ "Connector runtime has not acquired its leader lease after ~30s — " +
216
+ "Redis unreachable (check REDIS_URL points at the SAME instance " +
217
+ "as the API) or another replica holds it; if none does, the " +
218
+ "gateway will not connect.",
219
+ { connectorId },
220
+ );
221
+ }
201
222
  }
202
223
  } else {
203
224
  const renewed = await renewLeaderLease({
@@ -97,7 +97,10 @@ const connectInfoSchema = z.object({
97
97
  apiPublicUrl: z.string(),
98
98
  redirectUri: z.string(),
99
99
  interactionsUrl: z.string(),
100
- ingressSecretConfigured: z.boolean(),
100
+ // @deprecated legacy standalone-gateway signal (Boolean(CONNECTOR_INGRESS_SECRET)).
101
+ // The default inline runtime never uses the ingress secret; readiness is
102
+ // `workerOnline` (the owned Redis heartbeat). Kept one minor; NOT a precondition.
103
+ legacyIngressSecretConfigured: z.boolean(),
101
104
  credentialStored: z.boolean(),
102
105
  guildId: z.string().nullable(),
103
106
  // Tri-state — null = unknown (no guild from worker or derived credential).
@@ -420,7 +423,7 @@ export const adminConnectorsRouter = new OpenAPIHono<AppEnv>()
420
423
  apiPublicUrl,
421
424
  redirectUri,
422
425
  interactionsUrl: `${apiPublicUrl}/v1/connectors/discord/interactions`,
423
- ingressSecretConfigured: Boolean(env.CONNECTOR_INGRESS_SECRET),
426
+ legacyIngressSecretConfigured: Boolean(env.CONNECTOR_INGRESS_SECRET),
424
427
  credentialStored: derived !== null,
425
428
  guildId,
426
429
  // Tri-state — a guild id (live or derived) confirms install; else null.