@teamscala/service-boot 0.23.286 → 0.23.288

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
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.286",
9
+ "version": "0.23.288",
10
10
  "sideEffects": false,
11
11
  "files": [
12
12
  "src"
@@ -19,6 +19,7 @@
19
19
  "@teamscala/auth": "*",
20
20
  "@teamscala/aws": "*",
21
21
  "@teamscala/cache": "*",
22
+ "@teamscala/channel": "*",
22
23
  "@teamscala/chat-completion": "*",
23
24
  "@teamscala/db": "*",
24
25
  "@teamscala/db-validation": "*",
@@ -448,6 +449,14 @@
448
449
  "types": "./src/handlers/bun-stable-watch.d.ts",
449
450
  "default": "./src/handlers/bun-stable-watch.ts"
450
451
  },
452
+ "./handlers/pane-outbox-drain": {
453
+ "types": "./src/handlers/pane-outbox-drain.d.ts",
454
+ "default": "./src/handlers/pane-outbox-drain.ts"
455
+ },
456
+ "./handlers/pane-outbox-drain.ts": {
457
+ "types": "./src/handlers/pane-outbox-drain.d.ts",
458
+ "default": "./src/handlers/pane-outbox-drain.ts"
459
+ },
451
460
  "./handlers/projects-digest": {
452
461
  "types": "./src/handlers/projects-digest.d.ts",
453
462
  "default": "./src/handlers/projects-digest.ts"
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ /**
2
+ * @system server-boot
3
+ * @status handwritten
4
+ * @edit edit directly
5
+ *
6
+ * The `pane-outbox-drain` pg-boss handler — delivers messages the operator's
7
+ * WhatsApp PA queues into `pane_messages` to the addressed agent pane.
8
+ *
9
+ * WHY THE REGISTRATION LIVES HERE AND THE WORK LIVES IN @teamscala/channel.
10
+ * The delivery itself belongs to the channel capability, so `paneOutboxDrainTick`
11
+ * is owned by `@teamscala/channel` beside the dispatch handler it calls. But
12
+ * `pgBossHandlerRegistry` is SERVER tier and `@teamscala/channel` is ADAPTER
13
+ * tier, so the channel package cannot register a pg-boss handler without an
14
+ * upward dependency — forbidden by `vertical-dependency-only`, which has no
15
+ * carve-out. server → adapter is DOWNWARD, so the registration sits here and
16
+ * imports the work, exactly as the four sibling handlers in this directory do.
17
+ *
18
+ * The MountContext route (`ctx.registerScheduledJob`) is tier-legal but NOT
19
+ * usable: `start-scheduled-jobs.ts` offers only `long_running` (invoked once,
20
+ * runs forever) and `recurring` (whose handlers "manage their own setInterval").
21
+ * Both are the in-process timer that `pg-boss-for-background-work` forbids and
22
+ * that the timer half of `in-memory-state-is-not-durable` describes — a timer
23
+ * only fires if its host outlives it, so on a restart-prone service it silently
24
+ * never runs while logging that it started. pg-boss is the only durable option,
25
+ * because the job row outlives any worker.
26
+ *
27
+ * Scoped to scala-channel by its `scheduled_job` row's `service_slug`, so it
28
+ * runs on the service that owns the channel rather than on scala-agents, whose
29
+ * UI-asset build failures used to take PA delivery down with them.
30
+ *
31
+ * Registered under slug `@teamscala/service-boot:pane-outbox-drain`.
32
+ */
33
+ import { paneOutboxDrainTick } from "@teamscala/channel/pane-outbox/drain-tick";
34
+ import { pgBossHandlerRegistry } from "../pg-boss-handler-registry.ts";
35
+
36
+ pgBossHandlerRegistry.register(
37
+ "@teamscala/service-boot:pane-outbox-drain",
38
+ paneOutboxDrainTick,
39
+ );
@@ -20,6 +20,7 @@ import "./handlers/upstream-issue-sync.ts";
20
20
  import "./handlers/scheduled-browser-tests.ts";
21
21
  import "./handlers/scala-tools-currency-watch.ts";
22
22
  import "./handlers/registry-stale-inactive-sweep.ts";
23
+ import "./handlers/pane-outbox-drain.ts";
23
24
  export interface PgBossMountContext {
24
25
  databaseUrl: string;
25
26
  serviceSlug: string;
@@ -33,6 +33,7 @@ import "./handlers/upstream-issue-sync.ts";
33
33
  import "./handlers/scheduled-browser-tests.ts";
34
34
  import "./handlers/scala-tools-currency-watch.ts";
35
35
  import "./handlers/registry-stale-inactive-sweep.ts";
36
+ import "./handlers/pane-outbox-drain.ts";
36
37
 
37
38
  export interface PgBossMountContext {
38
39
  databaseUrl: string;
@@ -224,6 +225,28 @@ export async function mountPgBoss(
224
225
  handlerSlug: config.handler_slug,
225
226
  });
226
227
  }
228
+
229
+ // A SUCCESSFUL mount emitted nothing until now, while every failure
230
+ // path above recorded an event — so the one state you most need to
231
+ // confirm ("is this job actually scheduled on this service?") was the
232
+ // only one with no evidence. Combined with resolveHandler THROWING
233
+ // into boss.work() rather than warning, that made "never scheduled"
234
+ // and "fired and threw" externally identical, and cost a
235
+ // scheduled_job relocation that had to be reverted undiagnosed on
236
+ // 2026-07-30. Emitting on success makes the mount set queryable
237
+ // (event-log-is-the-only-durable-event-log).
238
+ const mounted = jobs[jobs.length - 1];
239
+ recordEvent({
240
+ kind: "pgboss.job.mounted",
241
+ payload: {
242
+ service: ctx.serviceSlug,
243
+ slug: entry.slug,
244
+ queue: queueName,
245
+ handler: config.handler_slug,
246
+ cron: mounted?.cron ?? null,
247
+ interval_seconds: mounted?.intervalSeconds ?? null,
248
+ },
249
+ });
227
250
  } catch (err) {
228
251
  // crash-isolation for the mount path: a work()/schedule() failure
229
252
  // (e.g. DB not open, queue conflict) degrades to "this job unavailable"