refactor(jobs): declare every queue in a typed registry and load handlers on first job

The five hand-written registrars (about 5,500 lines, one 997-line function)
are replaced by `jobs/registry/`: one `defineJob` entry per queue with its
cron, schedule send options, policy, work options, lock, lane and a handler
loader. `registerJobGroup` creates, schedules, retires and binds each group
in the order the registrars used; the worker boot walks the groups and runs
their boot discovery.

Handlers load on their queue's first job. The facts a declaration needs at
boot (names, crons, concurrency, send options) moved out of the heavy job
modules into light `*-queue.ts` siblings, and their importers point there,
so loading the registry evaluates 288 modules instead of the 1,164 the
registrars pulled in. The formerly inline handlers moved verbatim into
handler modules under `jobs/reminder/`.

Fitbit's queues live in `fitbit/jobs/definitions.ts` and enter the registry
through one spread, so removing the integration later is that folder and
that line.

`tests/integration/queue-registration-parity.test.ts` proves pg-boss holds
exactly what the registrars left (queues, schedules, bindings, byte for
byte) and loads every handler once. The queue tests and the runtime guard
assert on the registry objects instead of reading registrar source.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 12:36:09 +02:00
1 parent df6e182e0b
commit 4807a70c1d
193 files changed
+6830 -7950

No files matched your search

+8 -13
View File
@@ -1,7 +1,7 @@
{
"$comment": "Production files over 800 lines and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 121,
"total": 143770,
"count": 116,
"total": 137648,
"entries": {
"app/api/dashboard/summary/route.ts": 1032,
"app/api/documents/inbound/route.ts": 811,
@@ -89,18 +89,13 @@
"lib/insights/mood-status.ts": 814,
"lib/insights/status-cache.ts": 812,
"lib/integrations/status.ts": 1445,
"lib/jobs/apple-health-import-worker.ts": 801,
"lib/jobs/backup-restore.ts": 888,
"lib/jobs/coach-nudge.ts": 1089,
"lib/jobs/insight-pregenerate.ts": 1266,
"lib/jobs/medication-intake-import.ts": 822,
"lib/jobs/offhost-backup.ts": 1168,
"lib/jobs/backup-restore.ts": 887,
"lib/jobs/coach-nudge.ts": 1084,
"lib/jobs/insight-pregenerate.ts": 1259,
"lib/jobs/medication-intake-import.ts": 819,
"lib/jobs/offhost-backup.ts": 1147,
"lib/jobs/reaction-line.ts": 824,
"lib/jobs/reminder/medication-reminder-check.ts": 818,
"lib/jobs/reminder/register-integration-sync.ts": 1051,
"lib/jobs/reminder/register-maintenance.ts": 2197,
"lib/jobs/reminder/register-rollup.ts": 1156,
"lib/jobs/reminder/register-status.ts": 878,
"lib/managed-profiles/handover.ts": 986,
"lib/mcp/record-search.ts": 1467,
"lib/mcp/rich-reads.ts": 1467,
@@ -117,7 +112,7 @@
"lib/rollups/measurement-rollups.ts": 1496,
"lib/search/search-records.ts": 996,
"lib/sharing/grants.ts": 1046,
"lib/support/report.ts": 905,
"lib/support/report.ts": 903,
"lib/telegram-webhook-handlers.ts": 1341,
"lib/validations/inbound-documents.ts": 1130,
"lib/whoop/client.ts": 917,
@@ -1,7 +1,7 @@
{
"$comment": "Production functions over 200 lines, keyed \"file :: name\", and their length. Maintained by src/__tests__/source-size-ratchet.test.ts.",
"count": 410,
"total": 147795,
"count": 403,
"total": 144626,
"entries": {
"app/achievements/page.tsx :: AchievementsPage": 206,
"app/api/admin/backups/upload/route.ts :: apiHandler(arg)": 314,
@@ -368,13 +368,6 @@
"lib/jobs/reaction-line.ts :: runReactionLine": 337,
"lib/jobs/reminder/medication-reminder-check.ts :: handleReminderCheck": 619,
"lib/jobs/reminder/medication-reminder-check.ts :: handleReminderCheck > withBackgroundEvent(arg)": 614,
"lib/jobs/reminder/register-integration-sync.ts :: enqueueIntegrationSyncBootDiscovery": 224,
"lib/jobs/reminder/register-integration-sync.ts :: registerIntegrationSyncQueues": 320,
"lib/jobs/reminder/register-maintenance.ts :: enqueueMaintenanceBootDiscovery": 232,
"lib/jobs/reminder/register-maintenance.ts :: registerMaintenanceQueues": 997,
"lib/jobs/reminder/register-rollup.ts :: enqueueRollupBootDiscovery": 238,
"lib/jobs/reminder/register-rollup.ts :: registerRollupQueues": 616,
"lib/jobs/reminder/register-status.ts :: registerStatusQueues": 542,
"lib/jobs/workout-insight-generate.ts :: runWorkoutInsightGenerate": 324,
"lib/labs/biomarker-merge.ts :: runMerge": 254,
"lib/labs/ocr-extract.ts :: runOcrExtraction": 207,
+2 -20
View File
@@ -1,7 +1,7 @@
{
"$comment": "tests that read a source file by path, per test file under src/. Maintained by src/__tests__/test-realism-ratchet.test.ts.",
"count": 367,
"total": 367,
"count": 349,
"total": 349,
"entries": {
"__tests__/account-payload-consumer-guard.test.ts": 1,
"__tests__/admin-data-wipe-completeness.test.ts": 1,
@@ -314,44 +314,26 @@
"lib/jobs/__tests__/ai-job-capability-inventory.test.ts": 1,
"lib/jobs/__tests__/apple-health-import-ecg.test.ts": 1,
"lib/jobs/__tests__/apple-health-import-export-date.test.ts": 1,
"lib/jobs/__tests__/apple-health-import-reconcile.test.ts": 1,
"lib/jobs/__tests__/apple-health-import-send-policy.test.ts": 1,
"lib/jobs/__tests__/coach-memory-refresh-queue.test.ts": 1,
"lib/jobs/__tests__/coach-message-cleanup-queue.test.ts": 1,
"lib/jobs/__tests__/cumulative-pr-rederive-queue.test.ts": 1,
"lib/jobs/__tests__/cycle-reminder-queue.test.ts": 1,
"lib/jobs/__tests__/daily-briefing.test.ts": 1,
"lib/jobs/__tests__/data-arrival-provider-isolation.test.ts": 1,
"lib/jobs/__tests__/data-arrival-queue.test.ts": 1,
"lib/jobs/__tests__/data-backup-send-policy.test.ts": 1,
"lib/jobs/__tests__/drain-cumulative-queue.test.ts": 1,
"lib/jobs/__tests__/fitbit-queues.test.ts": 1,
"lib/jobs/__tests__/geo-backfill.test.ts": 1,
"lib/jobs/__tests__/geolite2-fetch-queue.test.ts": 1,
"lib/jobs/__tests__/google-health-queues.test.ts": 1,
"lib/jobs/__tests__/insight-pregenerate.test.ts": 1,
"lib/jobs/__tests__/insight-status-generate.test.ts": 1,
"lib/jobs/__tests__/intake-slot-dedup-queue.test.ts": 1,
"lib/jobs/__tests__/integration-backfill-admission.test.ts": 1,
"lib/jobs/__tests__/job-failures-retention.test.ts": 1,
"lib/jobs/__tests__/job-outcome-source-inventory.test.ts": 1,
"lib/jobs/__tests__/mcp-token-cleanup-queue.test.ts": 1,
"lib/jobs/__tests__/mean-consolidation-queue.test.ts": 1,
"lib/jobs/__tests__/measurement-reminder-queue.test.ts": 1,
"lib/jobs/__tests__/measurement-tombstone-cleanup.test.ts": 1,
"lib/jobs/__tests__/nightscout-queues.test.ts": 1,
"lib/jobs/__tests__/period-narrative-warm.test.ts": 1,
"lib/jobs/__tests__/queue-policy-tables.test.ts": 1,
"lib/jobs/__tests__/read-side-effect-queues.test.ts": 1,
"lib/jobs/__tests__/recovery-score-queue.test.ts": 1,
"lib/jobs/__tests__/sleep-timeline-queues.test.ts": 1,
"lib/jobs/__tests__/status-cron-candidates.test.ts": 1,
"lib/jobs/__tests__/step-consolidation-repair-queue.test.ts": 1,
"lib/jobs/__tests__/stress-strain-retention-queue.test.ts": 1,
"lib/jobs/__tests__/subsystem-surface.test.ts": 1,
"lib/jobs/__tests__/tls-pin-monitor-queue.test.ts": 1,
"lib/jobs/__tests__/whoop-queues.test.ts": 1,
"lib/jobs/__tests__/withings-ecg-worker.test.ts": 1,
"lib/jobs/__tests__/withings-queues.test.ts": 1,
"lib/jobs/__tests__/workout-insight-queue.test.ts": 1,
"lib/jobs/reminder/__tests__/integration-cohort-outcomes.test.ts": 1,
@@ -1,7 +1,7 @@
{
"$comment": "Comment lines that open with a release version, per file under src/. Maintained by src/__tests__/version-stamp-ratchet.test.ts.",
"count": 2954,
"total": 10499,
"count": 2924,
"total": 10176,
"entries": {
"__tests__/account-payload-consumer-guard.test.ts": 2,
"__tests__/acting-account-boundary-guard.test.ts": 7,
@@ -2288,59 +2288,34 @@
"lib/integrations/metric-freshness.ts": 1,
"lib/integrations/status.ts": 19,
"lib/intl/__tests__/formatter-cache.test.ts": 1,
"lib/jobs/__tests__/apple-health-import-reconcile.test.ts": 2,
"lib/jobs/__tests__/apple-health-import-send-policy.test.ts": 1,
"lib/jobs/__tests__/coach-memory-refresh-queue.test.ts": 2,
"lib/jobs/__tests__/coach-message-cleanup-queue.test.ts": 1,
"lib/jobs/__tests__/apple-health-import-reconcile.test.ts": 1,
"lib/jobs/__tests__/coach-nudge.test.ts": 3,
"lib/jobs/__tests__/coach-reminder-sweep.test.ts": 1,
"lib/jobs/__tests__/cumulative-pr-rederive-queue.test.ts": 1,
"lib/jobs/__tests__/cycle-reminder-queue.test.ts": 2,
"lib/jobs/__tests__/cycle-reminder.test.ts": 2,
"lib/jobs/__tests__/data-arrival-queue.test.ts": 1,
"lib/jobs/__tests__/drain-cumulative-queue.test.ts": 4,
"lib/jobs/__tests__/feedback-aggregator.test.ts": 2,
"lib/jobs/__tests__/fitbit-backfill.test.ts": 1,
"lib/jobs/__tests__/fitbit-queues.test.ts": 2,
"lib/jobs/__tests__/geo-backfill.test.ts": 3,
"lib/jobs/__tests__/google-health-queues.test.ts": 1,
"lib/jobs/__tests__/geo-backfill.test.ts": 1,
"lib/jobs/__tests__/google-health-sleep-repair.test.ts": 1,
"lib/jobs/__tests__/hero-line-json-envelope.test.ts": 1,
"lib/jobs/__tests__/insight-pregenerate-retry-enqueue.test.ts": 1,
"lib/jobs/__tests__/insight-pregenerate.test.ts": 16,
"lib/jobs/__tests__/insight-status-generate.test.ts": 2,
"lib/jobs/__tests__/insight-pregenerate.test.ts": 14,
"lib/jobs/__tests__/intake-auto-skip.test.ts": 3,
"lib/jobs/__tests__/intake-auto-take.test.ts": 1,
"lib/jobs/__tests__/intake-slot-dedup-queue.test.ts": 6,
"lib/jobs/__tests__/job-run-report.test.ts": 1,
"lib/jobs/__tests__/mean-consolidation-queue.test.ts": 2,
"lib/jobs/__tests__/measurement-reminder-queue.test.ts": 2,
"lib/jobs/__tests__/measurement-reminder.test.ts": 6,
"lib/jobs/__tests__/measurement-tombstone-cleanup.test.ts": 2,
"lib/jobs/__tests__/medication-inventory-expire.test.ts": 1,
"lib/jobs/__tests__/medication-low-stock.test.ts": 2,
"lib/jobs/__tests__/medication-reminder-check.test.ts": 2,
"lib/jobs/__tests__/mood-reminder.test.ts": 2,
"lib/jobs/__tests__/nightscout-queues.test.ts": 3,
"lib/jobs/__tests__/period-narrative-warm.test.ts": 1,
"lib/jobs/__tests__/reaction-line-degradation.test.ts": 2,
"lib/jobs/__tests__/recovery-score-queue.test.ts": 2,
"lib/jobs/__tests__/recovery-score.test.ts": 1,
"lib/jobs/__tests__/reminder-phases.test.ts": 1,
"lib/jobs/__tests__/reminder-satisfy.test.ts": 3,
"lib/jobs/__tests__/report-worker-error.test.ts": 1,
"lib/jobs/__tests__/run-job.test.ts": 1,
"lib/jobs/__tests__/sleep-timeline-backfill.test.ts": 1,
"lib/jobs/__tests__/sleep-timeline-queues.test.ts": 2,
"lib/jobs/__tests__/status-cron-candidates.test.ts": 2,
"lib/jobs/__tests__/step-consolidation-repair-queue.test.ts": 1,
"lib/jobs/__tests__/stress-strain-retention-queue.test.ts": 2,
"lib/jobs/__tests__/tls-pin-monitor-queue.test.ts": 2,
"lib/jobs/__tests__/whoop-backfill.test.ts": 1,
"lib/jobs/__tests__/whoop-queues.test.ts": 2,
"lib/jobs/__tests__/whoop-resource-sync.test.ts": 1,
"lib/jobs/__tests__/withings-queues.test.ts": 2,
"lib/jobs/__tests__/workout-insight-queue.test.ts": 1,
"lib/jobs/apple-health-import-worker.ts": 4,
"lib/jobs/coach-message-cleanup.ts": 1,
"lib/jobs/coach-nudge-ai.ts": 3,
@@ -2396,7 +2371,7 @@
"lib/jobs/recovery-score.ts": 1,
"lib/jobs/reminder-phases.ts": 1,
"lib/jobs/reminder-satisfy.ts": 1,
"lib/jobs/reminder-worker.ts": 4,
"lib/jobs/reminder-worker.ts": 3,
"lib/jobs/reminder/__tests__/cleanup-handlers-visibility.test.ts": 1,
"lib/jobs/reminder/__tests__/queue-retention.test.ts": 1,
"lib/jobs/reminder/cleanup-handlers.ts": 7,
@@ -2410,11 +2385,6 @@
"lib/jobs/reminder/oura-sync.ts": 1,
"lib/jobs/reminder/polar-sync.ts": 2,
"lib/jobs/reminder/poll-cohort.ts": 2,
"lib/jobs/reminder/register-integration-sync.ts": 63,
"lib/jobs/reminder/register-maintenance.ts": 114,
"lib/jobs/reminder/register-reminders.ts": 15,
"lib/jobs/reminder/register-rollup.ts": 37,
"lib/jobs/reminder/register-status.ts": 38,
"lib/jobs/reminder/registrar-shared.ts": 2,
"lib/jobs/reminder/strava-sync.ts": 1,
"lib/jobs/reminder/whoop-sync.ts": 3,
-2
View File
@@ -63,8 +63,6 @@ const NO_FLOOR_NEEDED: Readonly<Record<string, string>> = {
"named walks, each must positively match the keyset cursor",
"components/charts/__tests__/touch-action-guard.test.ts":
"named chart wrappers, each must positively declare touch-pan-y",
"__tests__/moodlog-removal-guard.test.ts":
"absence checks over named paths, columns and keys; no matcher to drift",
// Unit tests of runtime code whose name ends in "guard". They read no
// source and sweep nothing.
+41 -1
View File
@@ -197,6 +197,8 @@ export interface ModuleImports {
value: string[];
/** True when every statement is an `export … from` (or an import). */
reexportOnly: boolean;
/** The value imports evaluated when the module loads: `value` without `import("…")`. */
staticValue: string[];
}
/**
@@ -240,6 +242,7 @@ export function moduleImports(sf: ts.SourceFile): ModuleImports {
}
other += 1;
}
const staticValue = [...value];
const visit = (node: ts.Node): void => {
if (
ts.isCallExpression(node) &&
@@ -252,7 +255,7 @@ export function moduleImports(sf: ts.SourceFile): ModuleImports {
ts.forEachChild(node, visit);
};
visit(sf);
return { value, reexportOnly: reexports > 0 && other === 0 };
return { value, staticValue, reexportOnly: reexports > 0 && other === 0 };
}
export interface ImportGraph {
@@ -374,3 +377,40 @@ export function backEdgeCycles(edges: Map<string, string[]>): string[][] {
for (const v of [...edges.keys()].sort()) walk(v);
return cycles;
}
/**
* Every module under `src/` that loading `entries` evaluates: static value
* imports and re-exports, transitively. A dynamic `import("…")` is not
* followed, since it loads only when it runs. Paths are relative to `src/`.
*/
export function staticImportClosure(entries: readonly string[]): Set<string> {
const cache = ts.createModuleResolutionCache(
process.cwd(),
(x) => x,
COMPILER_OPTIONS,
);
const seen = new Set<string>();
const stack = [...entries];
while (stack.length > 0) {
const rel = stack.pop()!;
if (seen.has(rel)) continue;
seen.add(rel);
const abs = join(SRC, rel);
for (const spec of moduleImports(parse(rel, readFileSync(abs, "utf8")))
.staticValue) {
const hit = ts.resolveModuleName(
spec,
abs,
COMPILER_OPTIONS,
ts.sys,
cache,
).resolvedModule;
if (!hit || hit.isExternalLibraryImport) continue;
if (!hit.resolvedFileName.startsWith(`${SRC}/`)) continue;
const target = hit.resolvedFileName.slice(SRC.length + 1);
if (target.startsWith("generated/") || target.endsWith(".d.ts")) continue;
stack.push(target);
}
}
return seen;
}
+117 -188
View File
@@ -11,23 +11,23 @@
* `src/lib/jobs/queue-runtime.ts` declares every queue `short`, `budgeted` or
* `long`. This guard holds the tree to it:
*
* - every queue bound with `createAndWork` is in the table, and every table
* entry is bound;
* - every `boss.send` and every schedule tuple names a queue in the table;
* - every queue the worker registry declares (`jobs/registry/`) is in the
* table, and every table entry is declared;
* - every `boss.send` and every scheduled queue names a queue in the table;
* - a `long` queue has an expiry between the default and pg-boss's 24-hour
* ceiling, every send and schedule for it carries `expireInSeconds` (or an
* identifier the table names as its carrier), it stops on the job's budget
* where the table says (`jobBudget` / `jobDeadline`, or a declared
* one-shot), and it is exclusive per identity where the table says
* (`lockedPass` at the binding, or the named marker);
* ceiling, every send for it carries `expireInSeconds` (or an identifier
* the table names as its carrier) and its schedule's send options do, it
* stops on the job's budget where the table says (`jobBudget` /
* `jobDeadline`, or a declared one-shot), and it is exclusive per identity
* where the table says (the definition's `lock`, or the named marker);
* - a `budgeted` queue stops on the budget and relies on the default expiry;
* - a `short` queue's sends set no expiry of their own, which would mean the
* job is longer than the table claims.
*
* Floors fail the guard if a matcher stops matching, and the "plants" cases
* run the checks over a planted long queue that lacks each protection in turn
* (the mutation proof kept in the suite). Removing `lockedPass` from the
* pr-detection binding, or the expiry from its cron tuple, fails the real
* (the mutation proof kept in the suite). Removing the `lock` from the
* pr-detection definition, or the expiry from its schedule, fails the real
* checks below.
*/
import { describe, expect, it } from "vitest";
@@ -39,19 +39,14 @@ import {
DEFAULT_JOB_EXPIRE_SECONDS,
QUEUE_RUNTIME,
type QueueRuntime,
type SourceSite,
} from "@/lib/jobs/queue-runtime";
import { SCHEDULED_AND_SENT_QUEUES } from "@/lib/jobs/reminder/registrar-shared";
import { HEAVY_QUEUES } from "@/lib/jobs/heavy-lane";
import type { JobDefinition } from "@/lib/jobs/registry/define-job";
import { allJobDefinitions } from "@/lib/jobs/registry/index";
const SRC = join(process.cwd(), "src");
const REGISTRARS = [
"lib/jobs/reminder/register-integration-sync.ts",
"lib/jobs/reminder/register-maintenance.ts",
"lib/jobs/reminder/register-reminders.ts",
"lib/jobs/reminder/register-rollup.ts",
"lib/jobs/reminder/register-status.ts",
];
const DEFINITIONS = allJobDefinitions();
/** pg-boss refuses an expiry of 24 hours or more. */
const MAX_EXPIRE_SECONDS = 24 * 60 * 60;
@@ -120,27 +115,6 @@ interface Site {
text: string;
}
/** Every `createAndWork(boss, QUEUE, …)` in the registrars. */
function bindings(
texts: ReadonlyMap<string, string>,
definitions: Definitions,
): Site[] {
const out: Site[] = [];
for (const file of REGISTRARS) {
const text = code(texts.get(file) ?? "");
for (const m of text.matchAll(
/createAndWork\s*(?:<[^(]*?>)?\(\s*boss\s*,\s*([A-Z][A-Z0-9_]*)/g,
)) {
const open = text.indexOf("(", m.index);
const queue = resolve(definitions, file, m[1]);
if (queue) {
out.push({ file, queue, text: text.slice(open, closeOf(text, open)) });
}
}
}
return out;
}
/** Every `boss.send(QUEUE, …)` / `getGlobalBoss()?.send(QUEUE, …)`. */
function sends(
texts: ReadonlyMap<string, string>,
@@ -162,31 +136,6 @@ function sends(
return out;
}
/** Every `[QUEUE, CRON, options?]` tuple in a registrar's `schedules`. */
function schedules(
texts: ReadonlyMap<string, string>,
definitions: Definitions,
): Site[] {
const out: Site[] = [];
for (const file of REGISTRARS) {
const text = code(texts.get(file) ?? "");
const m = /const\s+schedules\s*:\s*ScheduleEntry\[\]\s*=\s*\[/.exec(text);
if (!m) continue;
const open = m.index + m[0].length - 1;
const body = text.slice(open + 1, closeOf(text, open) - 1);
for (let i = 0; i < body.length; i += 1) {
if (body[i] !== "[") continue;
const end = closeOf(body, i);
const tuple = body.slice(i, end);
const ident = /^\[\s*([A-Z][A-Z0-9_]*)/.exec(tuple)?.[1];
const queue = ident ? resolve(definitions, file, ident) : null;
if (queue) out.push({ file, queue, text: tuple });
i = end - 1;
}
}
return out;
}
/** The text of `fn` (a function or a const) in `file`. */
function bodyOf(
texts: ReadonlyMap<string, string>,
@@ -201,14 +150,6 @@ function bodyOf(
return text.slice(m.index, end < 0 ? undefined : end + 2);
}
function siteText(
texts: ReadonlyMap<string, string>,
site: SourceSite,
binding: Site | undefined,
): string {
return site === "binding" ? (binding?.text ?? "") : bodyOf(texts, site);
}
const STOPS = /\bjob(?:Budget|Deadline)\s*\(/;
/** Every finding for one queue's declaration against the tree. */
@@ -216,13 +157,12 @@ function check(
queue: string,
entry: QueueRuntime,
texts: ReadonlyMap<string, string>,
all: { bindings: Site[]; sends: Site[]; schedules: Site[] },
all: { definitions: readonly JobDefinition[]; sends: Site[] },
): string[] {
const problems: string[] = [];
const binding = all.bindings.find((b) => b.queue === queue);
const carriers = [...all.sends, ...all.schedules].filter(
(s) => s.queue === queue,
);
const definition = all.definitions.find((d) => d.queue === queue);
const sends = all.sends.filter((s) => s.queue === queue);
const scheduledExpiry = definition?.scheduleOptions?.expireInSeconds;
if (entry.runtime === "long") {
if (
!(entry.expireInSeconds > DEFAULT_JOB_EXPIRE_SECONDS) ||
@@ -230,85 +170,86 @@ function check(
) {
problems.push(`${queue}: expiry ${entry.expireInSeconds}s out of range`);
}
for (const site of carriers) {
for (const site of sends) {
const carried =
/\bexpireInSeconds\b/.test(site.text) ||
entry.expiryVia.some((v) => new RegExp(`\\b${v}\\b`).test(site.text));
if (!carried) {
problems.push(
`${queue}: ${site.file} sends or schedules it without an expiry`,
);
problems.push(`${queue}: ${site.file} sends it without an expiry`);
}
}
if (definition?.cron && typeof scheduledExpiry !== "number") {
problems.push(`${queue}: its schedule carries no expiry`);
}
if (typeof entry.stop === "object" && "oneShot" in entry.stop) {
if (entry.stop.oneShot.length < 20) {
problems.push(`${queue}: a one-shot needs its reason`);
}
} else if (!STOPS.test(siteText(texts, entry.stop, binding))) {
} else if (!STOPS.test(bodyOf(texts, entry.stop))) {
problems.push(`${queue}: does not stop on the job's budget`);
}
if (entry.exclusive === "lockedPass") {
if (!/\blockedPass\s*\(/.test(binding?.text ?? "")) {
problems.push(`${queue}: binding holds no lockedPass`);
if (!definition?.lock) {
problems.push(`${queue}: definition holds no lock`);
}
} else if (
!siteText(texts, entry.exclusive.at, binding).includes(
entry.exclusive.marker,
)
!bodyOf(texts, entry.exclusive.at).includes(entry.exclusive.marker)
) {
problems.push(`${queue}: no "${entry.exclusive.marker}" where declared`);
}
} else {
if (entry.runtime === "budgeted") {
if (!STOPS.test(siteText(texts, entry.budget, binding))) {
if (!STOPS.test(bodyOf(texts, entry.budget))) {
problems.push(`${queue}: does not stop on the job's budget`);
}
}
for (const site of carriers) {
for (const site of sends) {
if (/\bexpireInSeconds\b/.test(site.text)) {
problems.push(
`${queue}: ${site.file} sets an expiry on a queue declared ${entry.runtime}`,
);
}
}
if (scheduledExpiry !== undefined) {
problems.push(
`${queue}: its schedule sets an expiry on a queue declared ${entry.runtime}`,
);
}
}
return problems;
}
function scan(texts: ReadonlyMap<string, string>) {
const definitions = definitionsOf(texts);
return {
bindings: bindings(texts, definitions),
sends: sends(texts, definitions),
schedules: schedules(texts, definitions),
};
return { sends: sends(texts, definitionsOf(texts)) };
}
describe("every queue declares its runtime", () => {
const all = scan(source);
const bound = new Set(all.bindings.map((b) => b.queue));
const all = { definitions: DEFINITIONS, ...scan(source) };
const declared = new Set(DEFINITIONS.map((d) => d.queue));
it("finds the bindings, sends and schedules it is meant to police", () => {
// Pinned below the counts on 2026-10-01 (112 / 64 / 74).
expect(all.bindings.length).toBeGreaterThanOrEqual(105);
it("finds the declarations, sends and schedules it is meant to police", () => {
// Pinned below the counts on 2026-10-23 (122 / 64 / 78).
expect(DEFINITIONS.length).toBeGreaterThanOrEqual(115);
expect(all.sends.length).toBeGreaterThanOrEqual(50);
expect(all.schedules.length).toBeGreaterThanOrEqual(70);
expect(DEFINITIONS.filter((d) => d.cron).length).toBeGreaterThanOrEqual(70);
expect(
Object.values(QUEUE_RUNTIME).filter((e) => e.runtime === "long").length,
).toBeGreaterThanOrEqual(15);
});
it("every bound queue is in the table, and every entry is bound", () => {
expect([...bound].filter((q) => !(q in QUEUE_RUNTIME)).sort()).toEqual([]);
it("every declared queue is in the table, and every entry is declared", () => {
expect([...declared].filter((q) => !(q in QUEUE_RUNTIME)).sort()).toEqual(
[],
);
expect(
Object.keys(QUEUE_RUNTIME)
.filter((q) => !bound.has(q))
.filter((q) => !declared.has(q))
.sort(),
).toEqual([]);
});
it("every send and schedule names a queue in the table", () => {
const unknown = [...all.sends, ...all.schedules]
it("every send names a queue in the table", () => {
const unknown = all.sends
.filter((s) => !(s.queue in QUEUE_RUNTIME))
.map((s) => `${s.file} → ${s.queue}`);
expect(unknown).toEqual([]);
@@ -322,51 +263,56 @@ describe("every queue declares its runtime", () => {
});
it("plants: a long queue missing any protection is caught", () => {
const registrar = REGISTRARS[1];
const planted = (binding: string, schedule: string) =>
const file = "lib/jobs/planted.ts";
const texts = (handler: string) =>
new Map([
...source,
[
registrar,
`const PLANT_QUEUE = "planted-long";\n` +
`const schedules: ScheduleEntry[] = [\n ${schedule},\n];\n` +
`async function register(boss) {\n await createAndWork(boss, PLANT_QUEUE, { localConcurrency: 1 }, ${binding});\n}\n`,
],
[file, `export async function plant(jobs) {\n ${handler}\n}\n`],
]);
const entry: QueueRuntime = {
runtime: "long",
expireInSeconds: 3600,
expiryVia: [],
stop: "binding",
stop: { file, fn: "plant" },
exclusive: "lockedPass",
why: "planted",
};
const findings = (binding: string, schedule: string) => {
const texts = planted(binding, schedule);
return check("planted-long", entry, texts, scan(texts));
const planted = (overrides: Partial<JobDefinition>): JobDefinition => ({
queue: "planted-long",
cron: "0 3 * * *",
scheduleOptions: { expireInSeconds: 3600 },
work: { localConcurrency: 1 },
lock: () => "pass",
load: async () => async () => ({ ok: true, did: {} }),
...overrides,
});
const findings = (
handler: string,
overrides: Partial<JobDefinition> = {},
) => {
const t = texts(handler);
return check("planted-long", entry, t, {
definitions: [planted(overrides)],
...scan(t),
});
};
const safe = [
"lockedPass(PLANT_QUEUE, () => WHOLE_PASS, async (jobs) => run(jobBudget(jobs)))",
"[PLANT_QUEUE, CRON, { expireInSeconds: 3600 }]",
] as const;
expect(findings(...safe)).toEqual([]);
expect(findings(safe[0], "[PLANT_QUEUE, CRON]")).toEqual([
`planted-long: ${registrar} sends or schedules it without an expiry`,
const budgeted = "return run(jobBudget(jobs));";
expect(findings(budgeted)).toEqual([]);
expect(findings(budgeted, { scheduleOptions: { retryLimit: 0 } })).toEqual([
"planted-long: its schedule carries no expiry",
]);
expect(
findings("lockedPass(PLANT_QUEUE, () => WHOLE_PASS, run)", safe[1]),
).toEqual(["planted-long: does not stop on the job's budget"]);
expect(findings("async (jobs) => run(jobBudget(jobs))", safe[1])).toEqual([
"planted-long: binding holds no lockedPass",
expect(findings("return run(jobs);")).toEqual([
"planted-long: does not stop on the job's budget",
]);
expect(findings(budgeted, { lock: undefined })).toEqual([
"planted-long: definition holds no lock",
]);
expect(
check(
"planted-long",
{ ...entry, expireInSeconds: 900 },
...((): [ReadonlyMap<string, string>, ReturnType<typeof scan>] => {
const texts = planted(...safe);
return [texts, scan(texts)];
})(),
texts(budgeted),
{ definitions: [planted({})], ...scan(texts(budgeted)) },
),
).toEqual(["planted-long: expiry 900s out of range"]);
});
@@ -374,12 +320,13 @@ describe("every queue declares its runtime", () => {
/**
* Queues a file sends to through a variable (`boss.send(queueName, …)`): every
* known queue name the file spells out, as a constant or a map value, counts
* as sent, because the matcher above cannot tell which one the variable holds.
* known queue the file names, as a literal, a map value or an imported
* `*_QUEUE` constant, counts as sent, because the matcher above cannot tell which one the variable holds.
*/
function dynamicallySent(texts: ReadonlyMap<string, string>): Set<string> {
const definitions = definitionsOf(texts);
const out = new Set<string>();
for (const [, raw] of texts) {
for (const [file, raw] of texts) {
const text = code(raw);
if (
!/\b(?:boss|getGlobalBoss\(\)\??)\s*\.\s*send\s*(?:<[^(]*?>)?\(\s*[a-z]/.test(
@@ -391,13 +338,20 @@ function dynamicallySent(texts: ReadonlyMap<string, string>): Set<string> {
for (const m of text.matchAll(/"([a-z0-9-]+)"/g)) {
if (m[1] in QUEUE_RUNTIME) out.add(m[1]);
}
// A queue named through an imported constant counts the same.
for (const m of text.matchAll(/\b([A-Z][A-Z0-9_]*_QUEUE)\b/g)) {
const queue = resolve(definitions, file, m[1]);
if (queue && queue in QUEUE_RUNTIME) out.add(queue);
}
}
return out;
}
describe("a cron-fed queue polls slowly, a sent one does not", () => {
const all = scan(source);
const scheduled = new Set(all.schedules.map((s) => s.queue));
const scheduled = new Set(
DEFINITIONS.filter((d) => d.cron).map((d) => d.queue),
);
const sent = new Set([
...all.sends.map((s) => s.queue),
...dynamicallySent(source),
@@ -418,26 +372,14 @@ describe("a cron-fed queue polls slowly, a sent one does not", () => {
});
describe("heavy jobs share the lane", () => {
const all = scan(source);
const laned = all.bindings.filter((b) => /\bheavyLane\s*\(/.test(b.text));
const laned = DEFINITIONS.filter((d) => d.lane);
it("binds every heavy queue in the lane, and nothing else", () => {
expect(laned.map((b) => b.queue).sort()).toEqual(
it("puts every heavy queue in the lane, and nothing else", () => {
expect(laned.map((d) => d.queue).sort()).toEqual(
Object.keys(HEAVY_QUEUES).sort(),
);
});
it("holds a lockedPass outside the lane, so an expiry retry finds the run going", () => {
// Outside, a retry pg-boss starts while the expired run still holds the
// lane finds the lock and settles; inside, the lane would defer it and
// run the job a second time once the first is done.
const inverted = laned
.filter((b) => /\blockedPass\s*\(/.test(b.text))
.filter((b) => b.text.indexOf("lockedPass") > b.text.indexOf("heavyLane"))
.map((b) => b.queue);
expect(inverted).toEqual([]);
});
it("declares every heavy queue long, so its expiry, stop and exclusivity are proven", () => {
// A heavy job is the one most likely to run past pg-boss's default
// expiry, and the retry that follows an expiry runs it a second time.
@@ -464,41 +406,28 @@ describe("heavy jobs share the lane", () => {
// A deferred job returns from active to created. Under `short` (one
// queued job per key) and `stately` (one per state) a twin already
// queued would refuse it, and the job would fail instead of waiting.
const policies = policiesOf(source);
expect(policies.size).toBeGreaterThanOrEqual(40);
const refused = Object.keys(HEAVY_QUEUES)
.filter((queue) => /^(?:short|stately)$/.test(policies.get(queue) ?? ""))
.sort();
expect(refused).toEqual([]);
// The planted case: the same check over a table that declares one.
const planted = new Map(source);
const file = REGISTRARS[2];
planted.set(
file,
`const DATA_BACKUP_QUEUE = "data-backup";\nconst queuePolicies: QueuePolicyTable = {\n [DATA_BACKUP_QUEUE]: { policy: "short", reason: "x" },\n};\n`,
const refusing = (definitions: readonly JobDefinition[]) =>
definitions
.filter(
(d) => d.lane && /^(?:short|stately)$/.test(d.policy?.policy ?? ""),
)
.map((d) => d.queue)
.sort();
expect(DEFINITIONS.filter((d) => d.policy).length).toBeGreaterThanOrEqual(
40,
);
expect(policiesOf(planted).get("data-backup")).toBe("short");
expect(refusing(DEFINITIONS)).toEqual([]);
// The planted case: the same check over a heavy queue declared `short`.
expect(
refusing([
{
queue: "data-backup",
policy: { policy: "short", reason: "x" },
work: { localConcurrency: 1 },
lane: true,
load: async () => async () => ({ ok: true, did: {} }),
},
]),
).toEqual(["data-backup"]);
});
});
/** Every `[QUEUE]: { policy: "…" }` in a registrar's `queuePolicies`. */
function policiesOf(texts: ReadonlyMap<string, string>): Map<string, string> {
const definitions = definitionsOf(texts);
const out = new Map<string, string>();
for (const file of REGISTRARS) {
const text = code(texts.get(file) ?? "");
const m = /const\s+queuePolicies\s*:\s*QueuePolicyTable\s*=\s*\{/.exec(
text,
);
if (!m) continue;
const open = m.index + m[0].length - 1;
const body = text.slice(open, closeOf(text, open));
for (const entry of body.matchAll(
/\[\s*([A-Z][A-Z0-9_]*)\s*\]\s*:\s*\{\s*policy\s*:\s*"([a-z]+)"/g,
)) {
const queue = resolve(definitions, file, entry[1]);
if (queue) out.set(queue, entry[2]);
}
}
return out;
}
+5 -7
View File
@@ -18,11 +18,12 @@
*/
import { readFileSync, existsSync } from "node:fs";
import path from "node:path";
import { allJobDefinitions } from "@/lib/jobs/registry/index";
import { describe, expect, it } from "vitest";
import { INTEGRATION_CADENCE } from "@/lib/integrations/sync-verdict";
import { INTEGRATION_DISPLAY_NAMES } from "@/components/settings/integrations/integration-fallback-row";
import { readSource } from "@/__tests__/helpers/source-files";
const ROOT = path.resolve(__dirname, "../..");
@@ -76,12 +77,9 @@ describe("moodLog integration stays removed", () => {
});
it("registers no moodlog queue, cron or handler binding", () => {
const registrar = readSource(
"src/lib/jobs/reminder/register-integration-sync.ts",
"export async function registerIntegrationSyncQueues",
);
expect(registrar).not.toContain("moodlog");
expect(registrar).not.toContain("MoodLog");
const queues = allJobDefinitions().map((job) => job.queue);
expect(queues.length).toBeGreaterThan(100);
expect(queues.filter((queue) => /mood-?log/i.test(queue))).toEqual([]);
});
it("carries no moodlog integration key on the status envelope", () => {
+1 -1
View File
@@ -32,10 +32,10 @@ import {
import { readBackupPassActivity } from "@/lib/jobs/backup-pass-attempts";
import { summariseBackupSchedule } from "@/lib/jobs/backup-schedule-status";
import {
OFFHOST_BACKUP_QUEUE,
offhostBackupConfigured,
probeOffhostLifecycle,
} from "@/lib/jobs/offhost-backup";
import { OFFHOST_BACKUP_QUEUE } from "@/lib/jobs/offhost-backup-queue";
import {
classifyOffhostBackup,
OFFHOST_BACKUP_PERIOD_HOURS,
+1 -1
View File
@@ -31,10 +31,10 @@ import { annotate } from "@/lib/logging/context";
import { auditLog } from "@/lib/auth/audit";
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import {
ENCRYPTION_KEY_ROTATE_QUEUE,
ENCRYPTION_KEY_ROTATE_SINGLETON,
ENCRYPTION_KEY_ROTATE_EXPIRE_SECONDS,
} from "@/lib/jobs/encryption-key-rotate";
import { ENCRYPTION_KEY_ROTATE_QUEUE } from "@/lib/jobs/encryption-key-rotate-queue";
import { getActiveKeyId } from "@/lib/crypto";
import { NextRequest } from "next/server";
@@ -21,12 +21,12 @@ import { auditLog } from "@/lib/auth/audit";
import { checkRateLimit } from "@/lib/rate-limit";
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import {
APPLE_HEALTH_IMPORT_V2_QUEUE,
APPLE_HEALTH_IMPORT_KIND,
APPLE_HEALTH_IMPORT_PARSER_REVISION,
APPLE_HEALTH_IMPORT_SEND_OPTIONS,
type AppleHealthImportPayload,
} from "@/lib/jobs/apple-health-import-worker";
import { APPLE_HEALTH_IMPORT_V2_QUEUE } from "@/lib/jobs/apple-health-import-queue";
import { streamMultipartToDisk } from "@/lib/multipart/stream-to-disk";
import {
createImportJobUnlessBusy,
+1 -1
View File
@@ -15,7 +15,7 @@ import {
} from "@/lib/fitbit/client";
import { getUserFitbitCredentials } from "@/lib/fitbit/credentials";
import { FITBIT_OAUTH_STATE_COOKIE } from "@/lib/fitbit/oauth-state";
import { FITBIT_BACKFILL_QUEUE } from "@/lib/jobs/fitbit-backfill";
import { FITBIT_BACKFILL_QUEUE } from "@/lib/fitbit/jobs/queues";
import { enqueueWhenBossReady } from "@/lib/jobs/boss-ready";
import { markReconnected } from "@/lib/integrations/status";
import { Prisma } from "@/generated/prisma/client";
+1 -1
View File
@@ -17,7 +17,7 @@ import {
} from "@/lib/google-health/client";
import { getUserGoogleHealthCredentials } from "@/lib/google-health/credentials";
import { GOOGLE_HEALTH_OAUTH_STATE_COOKIE } from "@/lib/google-health/oauth-state";
import { GOOGLE_HEALTH_BACKFILL_QUEUE } from "@/lib/jobs/google-health-backfill";
import { GOOGLE_HEALTH_BACKFILL_QUEUE } from "@/lib/jobs/google-health-backfill-queue";
import { enqueueWhenBossReady } from "@/lib/jobs/boss-ready";
import { markReconnected } from "@/lib/integrations/status";
import { Prisma } from "@/generated/prisma/client";
@@ -30,12 +30,12 @@ import { emitSignal } from "@/lib/logging/signal";
import { checkRateLimit } from "@/lib/rate-limit";
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import {
APPLE_HEALTH_IMPORT_V2_QUEUE,
APPLE_HEALTH_IMPORT_KIND,
APPLE_HEALTH_IMPORT_PARSER_REVISION,
APPLE_HEALTH_IMPORT_SEND_OPTIONS,
type AppleHealthImportPayload,
} from "@/lib/jobs/apple-health-import-worker";
import { APPLE_HEALTH_IMPORT_V2_QUEUE } from "@/lib/jobs/apple-health-import-queue";
import { streamMultipartToDisk } from "@/lib/multipart/stream-to-disk";
import {
createImportJobUnlessBusy,
@@ -28,10 +28,10 @@ import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import {
HEALTH_CONNECT_IMPORT_KIND,
HEALTH_CONNECT_IMPORT_PARSER_REVISION,
HEALTH_CONNECT_IMPORT_QUEUE,
HEALTH_CONNECT_IMPORT_SEND_OPTIONS,
type HealthConnectImportPayload,
} from "@/lib/jobs/health-connect-import-worker";
import { HEALTH_CONNECT_IMPORT_QUEUE } from "@/lib/jobs/health-connect-import-queue";
import { streamMultipartToDisk } from "@/lib/multipart/stream-to-disk";
import {
createImportJobUnlessBusy,
@@ -63,10 +63,8 @@ import { auditLog } from "@/lib/auth/audit";
import { getSession } from "@/lib/auth/session";
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import { checkRateLimit } from "@/lib/rate-limit";
import {
MEDICATION_INTAKE_IMPORT_QUEUE,
MEDICATION_INTAKE_IMPORT_STALE_AFTER_MS,
} from "@/lib/jobs/medication-intake-import";
import { MEDICATION_INTAKE_IMPORT_STALE_AFTER_MS } from "@/lib/jobs/medication-intake-import";
import { MEDICATION_INTAKE_IMPORT_QUEUE } from "@/lib/jobs/medication-intake-import-queue";
const SESSION_OK = {
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
+2 -4
View File
@@ -27,10 +27,8 @@ import { markReconnected } from "@/lib/integrations/status";
import { OURA_SYNC_QUEUE } from "@/lib/jobs/integration-poll-queues";
import { enqueueWhenBossReady } from "@/lib/jobs/boss-ready";
import { integrationBackfillSourceOptions } from "@/lib/jobs/integration-backfill-admission";
import {
OURA_BACKFILL_QUEUE,
type OuraBackfillPayload,
} from "@/lib/jobs/oura-backfill";
import { type OuraBackfillPayload } from "@/lib/jobs/oura-backfill";
import { OURA_BACKFILL_QUEUE } from "@/lib/jobs/oura-backfill-queue";
import { NextRequest, NextResponse } from "next/server";
/**
+1 -1
View File
@@ -18,7 +18,7 @@ import {
} from "@/lib/oauth/signed-state";
import { runOAuthCallbackOnce } from "@/lib/oauth/callback-claim";
import { markReconnected } from "@/lib/integrations/status";
import { STRAVA_BACKFILL_QUEUE } from "@/lib/jobs/strava-backfill";
import { STRAVA_BACKFILL_QUEUE } from "@/lib/jobs/strava-backfill-queue";
import { enqueueWhenBossReady } from "@/lib/jobs/boss-ready";
import { NextRequest, NextResponse } from "next/server";
+1 -1
View File
@@ -14,7 +14,7 @@ import {
import { getUserWhoopCredentials } from "@/lib/whoop/credentials";
import { WHOOP_OAUTH_STATE_COOKIE } from "@/lib/whoop/oauth-state";
import { buildReturnSchemeRedirect } from "@/lib/whoop/return-scheme";
import { WHOOP_BACKFILL_QUEUE } from "@/lib/jobs/whoop-backfill";
import { WHOOP_BACKFILL_QUEUE } from "@/lib/jobs/whoop-backfill-queue";
import { enqueueWhenBossReady } from "@/lib/jobs/boss-ready";
import { markReconnected } from "@/lib/integrations/status";
import { Prisma } from "@/generated/prisma/client";
+98
View File
@@ -0,0 +1,98 @@
/**
* Every queue the Fitbit integration runs, as one block of the worker
* registry.
*
* Google turns the classic Fitbit Web API off on 2026-10-30
* (`lib/fitbit/retirement.ts`). Until the integration is removed it keeps
* syncing; removing it then is this folder plus the one line in
* `jobs/registry/integration-sync.ts` that spreads these definitions in.
*/
import { bootStaggerSecondsFor } from "@/lib/jobs/integration-backfill-admission";
import { cronIsTheRetry } from "@/lib/jobs/reminder/registrar-shared";
import {
defineJob,
type BootDiscovery,
type JobDefinition,
} from "@/lib/jobs/registry/define-job";
import {
FITBIT_BACKFILL_CONCURRENCY,
FITBIT_BACKFILL_QUEUE,
FITBIT_OAUTH_STATE_CLEANUP_CRON,
FITBIT_OAUTH_STATE_CLEANUP_QUEUE,
FITBIT_SLEEP_REPAIR_CONCURRENCY,
FITBIT_SLEEP_REPAIR_QUEUE,
FITBIT_SYNC_CRON,
FITBIT_SYNC_QUEUE,
} from "./queues";
export const FITBIT_JOBS: readonly JobDefinition[] = [
// The cron walks every connection; serial so a backfill-heavy tick never
// crowds the request pool.
defineJob({
queue: FITBIT_SYNC_QUEUE,
cron: FITBIT_SYNC_CRON,
work: { localConcurrency: 1 },
load: async () =>
(await import("@/lib/jobs/reminder/fitbit-sync")).handleFitbitSync,
}),
// The source queue keeps the per-connection singleton and forwards
// admitted work to the shared drain.
defineJob({
queue: FITBIT_BACKFILL_QUEUE,
policy: {
policy: "exclusive",
reason:
"Per-connection full-history backfill; boot-discovery driven and self-converging.",
},
work: { localConcurrency: FITBIT_BACKFILL_CONCURRENCY },
load: async ({ boss }) =>
(
await import("@/lib/jobs/integration-backfill-forward")
).forwardToBackfillAdmission(boss, "fitbit-backfill"),
}),
// One-shot sleep duplicate repair: a bounded sleep re-read per connection,
// forwarded to the shared drain.
defineJob({
queue: FITBIT_SLEEP_REPAIR_QUEUE,
policy: {
policy: "exclusive",
reason:
"Per-connection one-shot sleep re-read; discovery drops the connection once it is marked repaired.",
},
work: { localConcurrency: FITBIT_SLEEP_REPAIR_CONCURRENCY },
load: async ({ boss }) =>
(
await import("@/lib/jobs/integration-backfill-forward")
).forwardToBackfillAdmission(boss, "fitbit-sleep-repair"),
}),
defineJob({
queue: FITBIT_OAUTH_STATE_CLEANUP_QUEUE,
cron: FITBIT_OAUTH_STATE_CLEANUP_CRON,
scheduleOptions: cronIsTheRetry,
work: { localConcurrency: 1 },
load: async () =>
(await import("@/lib/jobs/reminder/fitbit-sync"))
.handleFitbitOAuthStateCleanup,
}),
];
/** The Fitbit backfill and sleep repair, offered again at every boot. */
export const FITBIT_BOOT_DISCOVERY: readonly BootDiscovery[] = [
{
name: "fitbit-backfill",
run: async () =>
(
await import("@/lib/jobs/fitbit-backfill")
).enqueueBootTimeFitbitBackfill(bootStaggerSecondsFor("fitbit-backfill")),
},
{
name: "fitbit-sleep-repair",
run: async () =>
(
await import("@/lib/jobs/fitbit-sleep-repair")
).enqueueBootTimeFitbitSleepRepair(
bootStaggerSecondsFor("fitbit-sleep-repair"),
),
},
];
+30
View File
@@ -0,0 +1,30 @@
/**
* The queue facts of the Fitbit jobs: names and concurrency. They live apart
* from the handlers so the worker registry and every sender can read them
* without loading the Fitbit sync.
*/
export const FITBIT_SLEEP_REPAIR_QUEUE = "fitbit-sleep-repair";
/**
* Serial concurrency — a repair walks a year of sleep history for one account
* in 30-day requests against the tight per-user budget; concurrency-1 keeps it
* from crowding the request pool, matching `FITBIT_BACKFILL_CONCURRENCY`.
*/
export const FITBIT_SLEEP_REPAIR_CONCURRENCY = 1;
export const FITBIT_BACKFILL_QUEUE = "fitbit-backfill";
/**
* Serial concurrency — a backfill walks years of history for one account and is
* rate-bounded by Google's per-app quota; concurrency-1 keeps it from crowding
* the request pool, matching `WHOOP_BACKFILL_CONCURRENCY`.
*/
export const FITBIT_BACKFILL_CONCURRENCY = 1;
/** Hourly Fitbit poll (no webhook). */
export const FITBIT_SYNC_QUEUE = "fitbit-sync";
export const FITBIT_SYNC_CRON = "8 * * * *";
/** Daily sweep of the Fitbit OAuth state ledger. */
export const FITBIT_OAUTH_STATE_CLEANUP_QUEUE = "fitbit-oauth-state-cleanup";
export const FITBIT_OAUTH_STATE_CLEANUP_CRON = "24 3 * * *";
@@ -25,10 +25,10 @@ vi.mock("@/lib/jobs/boss-instance", () => ({
import {
APPLE_HEALTH_IMPORT_PARSER_REVISION,
APPLE_HEALTH_IMPORT_SEND_OPTIONS,
APPLE_HEALTH_IMPORT_V2_QUEUE,
migrateLegacyAppleHealthImport,
type AppleHealthImportPayload,
} from "../apple-health-import-worker";
import { APPLE_HEALTH_IMPORT_V2_QUEUE } from "@/lib/jobs/apple-health-import-queue";
const payload: AppleHealthImportPayload = {
userId: "user-1",
@@ -1,5 +1,5 @@
/**
* v1.32.1 (issue #588) — periodic orphan-`ImportJob` reconcile.
* Periodic orphan-`ImportJob` reconcile (issue #588).
*
* Before this change `reconcileOrphanImportJobs()` only ran once, at
* worker boot. A worker that crashed mid-extraction (OOM, restart) and
@@ -11,10 +11,9 @@
* that one boot-time pass. These tests cover both the periodic-tick
* wiring (queue provisioned, cron scheduled, handler bound) and the
* reconcile logic itself (a genuinely-abandoned row flips to `failed`;
* a row another worker is still actively running does not).
* a row another worker is still actively running does not). The wiring is
* read off the worker registry.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
@@ -45,19 +44,22 @@ vi.mock("@/lib/jobs/boss-instance", () => ({
import {
APPLE_HEALTH_IMPORT_PARSER_REVISION,
APPLE_HEALTH_IMPORT_V2_QUEUE,
IMPORT_JOB_RECONCILE_CRON,
IMPORT_JOB_RECONCILE_QUEUE,
handleImportJobReconcileTick,
reconcileOrphanImportJobs,
stagedImportFilesInUse,
} from "../apple-health-import-worker";
import {
APPLE_HEALTH_IMPORT_V2_QUEUE,
IMPORT_JOB_RECONCILE_CRON,
IMPORT_JOB_RECONCILE_QUEUE,
} from "@/lib/jobs/apple-health-import-queue";
import { sweepStaleImportStaging } from "@/lib/import/apple-health-staging";
const maintenanceSource = readFileSync(
join(process.cwd(), "src/lib/jobs/reminder/register-maintenance.ts"),
"utf8",
);
import { cronIsTheRetry } from "@/lib/jobs/reminder/registrar-shared";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
beforeEach(() => {
vi.resetAllMocks();
@@ -66,30 +68,21 @@ beforeEach(() => {
});
describe("periodic reconcile — wiring", () => {
it("provisions the queue, schedules a 15-minute cron, and binds the handler", () => {
it("provisions the queue, schedules a 15-minute cron, and binds the handler", async () => {
expect(IMPORT_JOB_RECONCILE_QUEUE).toBe("apple-health-import-reconcile");
expect(IMPORT_JOB_RECONCILE_CRON).toBe("*/15 * * * *");
const allQueues = maintenanceSource.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueues).not.toBeNull();
expect(allQueues![1]).toMatch(/\bIMPORT_JOB_RECONCILE_QUEUE\b/);
const schedules = maintenanceSource.match(
/const schedules[\s\S]*?=\s*\[([\s\S]*?)\];/,
);
expect(schedules).not.toBeNull();
// The third element pins the retry policy. It is load-bearing now that
expect(groupOf(IMPORT_JOB_RECONCILE_QUEUE).name).toBe("maintenance");
const job = jobOf(IMPORT_JOB_RECONCILE_QUEUE);
expect(job.cron).toBe(IMPORT_JOB_RECONCILE_CRON);
// The schedule options pin the retry policy. It is load-bearing now that
// the handler reports a failed outcome instead of swallowing: without
// `cronIsTheRetry` a database outage would run the same doomed sweep
// three times per tick, and the next tick is already the retry.
expect(schedules![1]).toMatch(
/\[IMPORT_JOB_RECONCILE_QUEUE,\s*IMPORT_JOB_RECONCILE_CRON,\s*cronIsTheRetry\]/,
);
expect(job.scheduleOptions).toBe(cronIsTheRetry);
expect(maintenanceSource).toMatch(
/createAndWork\(\s*boss,\s*IMPORT_JOB_RECONCILE_QUEUE[\s\S]{0,120}handleImportJobReconcileTick/,
expect(await loadedHandler(IMPORT_JOB_RECONCILE_QUEUE)).toBe(
handleImportJobReconcileTick,
);
});
});
@@ -1,6 +1,5 @@
/**
* v1.28.33 (issue #486) — send-policy pins for the Apple Health import
* queue.
* Send-policy pins for the Apple Health import queue (issue #486).
*
* The queue defaults (retryLimit 2, expireInSeconds 900) redelivered a
* long-running import after its staged `/tmp` upload was already
@@ -21,20 +20,27 @@ vi.mock("@/lib/db", () => ({
import {
APPLE_HEALTH_IMPORT_PARSER_REVISION,
APPLE_HEALTH_IMPORT_LEGACY_QUEUE,
APPLE_HEALTH_IMPORT_SEND_OPTIONS,
APPLE_HEALTH_IMPORT_V2_QUEUE,
} from "../apple-health-import-worker";
import {
APPLE_HEALTH_IMPORT_LEGACY_QUEUE,
APPLE_HEALTH_IMPORT_V2_QUEUE,
} from "@/lib/jobs/apple-health-import-queue";
import {
handleAppleHealthImportJobs,
handleLegacyAppleHealthImportJobs,
} from "@/lib/jobs/reminder/maintenance-handlers";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const ROUTE_FILES = [
"src/app/api/import/apple-health-export/route.ts",
"src/app/api/admin/import-apple-health-export/route.ts",
];
const maintenanceSource = readFileSync(
join(process.cwd(), "src/lib/jobs/reminder/register-maintenance.ts"),
"utf8",
);
const workerSource = readFileSync(
join(process.cwd(), "src/lib/jobs/apple-health-import-worker.ts"),
"utf8",
@@ -80,21 +86,18 @@ describe("apple health import — parser revision boundary", () => {
expect(APPLE_HEALTH_IMPORT_LEGACY_QUEUE).toBe("apple-health-import");
});
it("drains current-parser jobs separately while bridging legacy backlog", () => {
const allQueues = maintenanceSource.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
it("drains current-parser jobs separately while bridging legacy backlog", async () => {
expect(groupOf(APPLE_HEALTH_IMPORT_V2_QUEUE).name).toBe("maintenance");
expect(groupOf(APPLE_HEALTH_IMPORT_LEGACY_QUEUE).name).toBe("maintenance");
// Only the current-parser queue runs in the heavy lane; the legacy
// bridge re-sends its backlog onto it.
expect(jobOf(APPLE_HEALTH_IMPORT_V2_QUEUE).lane).toBe(true);
expect(jobOf(APPLE_HEALTH_IMPORT_LEGACY_QUEUE).lane).toBeUndefined();
expect(await loadedHandler(APPLE_HEALTH_IMPORT_V2_QUEUE)).toBe(
handleAppleHealthImportJobs,
);
expect(allQueues).not.toBeNull();
expect(allQueues![1]).toMatch(/\bAPPLE_HEALTH_IMPORT_V2_QUEUE\b/);
expect(allQueues![1]).toMatch(/\bAPPLE_HEALTH_IMPORT_LEGACY_QUEUE\b/);
// The v2 window is the wider of the two: its binding wraps the batch in
// a loop that keeps the first failed outcome, so the handler call sits
// further from the queue name than the legacy bridge's does.
expect(maintenanceSource).toMatch(
/createAndWork[\s\S]{0,200}APPLE_HEALTH_IMPORT_V2_QUEUE[\s\S]{0,600}handleAppleHealthImport/,
);
expect(maintenanceSource).toMatch(
/createAndWork[\s\S]{0,200}APPLE_HEALTH_IMPORT_LEGACY_QUEUE[\s\S]{0,400}migrateLegacyAppleHealthImport/,
expect(await loadedHandler(APPLE_HEALTH_IMPORT_LEGACY_QUEUE)).toBe(
handleLegacyAppleHealthImportJobs,
);
});
@@ -1,49 +1,35 @@
/**
* v1.11.1 — queue-registration guard for the combined Coach memory-refresh
* job (rolling conversation summary + durable fact extraction).
* Queue-registration guard for the combined Coach memory-refresh job
* (rolling conversation summary + durable fact extraction).
*
* Same source-text-grep approach as the period-narrative / stress-strain
* guards: assert the queue is imported, registered in `allQueues`, and wired
* to a `createAndWork` handler that runs the refresh pipeline — without booting
* pg-boss + Prisma. An unregistered queue silently never runs (the recurring
* past bug this guards against; v1.4.37 W10 caught exactly this class).
* Reads the worker registry: the queue is declared in the status group and
* its loader resolves to the handler that runs the refresh pipeline. An
* unregistered queue silently never runs (the dead-queue class).
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the coach-memory-refresh wiring moved out of the 2143-LOC
// reminder-worker boot file into the status registrar. The dead-queue guard
// follows the wiring there.
const REGISTRAR_PATH = join(__dirname, "..", "reminder", "register-status.ts");
const source = readFileSync(REGISTRAR_PATH, "utf8");
import { COACH_MEMORY_REFRESH_QUEUE } from "@/lib/ai/coach/coach-memory-shared";
import { handleCoachMemoryRefresh } from "@/lib/jobs/reminder/status-handlers";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
function allQueuesBlock(): string {
const m = source.match(/const allQueues\s*=\s*\[([\s\S]*?)\];/);
expect(m).not.toBeNull();
return m![1];
}
describe("reminder-worker — coach-memory-refresh wiring", () => {
it("imports the queue symbol + worker from the coach-memory modules", () => {
expect(source).toMatch(
/from\s*["']@\/lib\/ai\/coach\/coach-memory-shared["']/,
);
expect(source).toMatch(/\bCOACH_MEMORY_REFRESH_QUEUE\b/);
expect(source).toMatch(
/from\s*["']@\/lib\/ai\/coach\/coach-memory-refresh-worker["']/,
);
expect(source).toMatch(/\brunCoachMemoryRefresh\b/);
describe("coach-memory-refresh registration", () => {
it("declares the coach-memory-refresh queue in the status group", () => {
expect(groupOf(COACH_MEMORY_REFRESH_QUEUE).name).toBe("status");
});
it("registers the coach-memory-refresh queue in allQueues", () => {
expect(allQueuesBlock()).toMatch(/\bCOACH_MEMORY_REFRESH_QUEUE\b/);
it("runs one refresh at a time and has no cron", () => {
const job = jobOf(COACH_MEMORY_REFRESH_QUEUE);
expect(job.work).toEqual({ localConcurrency: 1 });
expect(job.cron).toBeUndefined();
});
it("registers a createAndWork handler that runs the refresh pipeline", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}COACH_MEMORY_REFRESH_QUEUE[\s\S]{0,600}runCoachMemoryRefresh/,
it("binds the handler that runs the refresh pipeline", async () => {
expect(await loadedHandler(COACH_MEMORY_REFRESH_QUEUE)).toBe(
handleCoachMemoryRefresh,
);
});
});
@@ -1,58 +1,50 @@
/**
* v1.18.7 — coach-message-cleanup queue registration guard.
* Coach-message-cleanup queue registration guard.
*
* Same source-text-grep approach as the other queue-wiring guards: assert the
* queue is registered in `allQueues`, scheduled, and wired to a `createAndWork`
* handler — without booting pg-boss + Prisma. An unregistered queue silently
* never drains (the recurring v1.4.37 dead-queue bug), which would leave the
* encrypted Coach history growing unbounded with the retention job dark.
* Reads the worker registry: the queue is declared in the maintenance group,
* scheduled with the cron as its retry, and bound to the cleanup handler. An
* unregistered queue silently never drains (the dead-queue class), which
* would leave the encrypted Coach history growing unbounded with the
* retention job dark.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-maintenance.ts",
import { handleCoachMessageCleanup } from "@/lib/jobs/reminder/cleanup-handlers";
import { cronIsTheRetry } from "@/lib/jobs/reminder/registrar-shared";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const QUEUE = "coach-message-cleanup";
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "cleanup-handlers.ts"),
"utf8",
);
const workerSource =
readFileSync(REGISTRAR_PATH, "utf8") +
readFileSync(
join(__dirname, "..", "reminder", "cleanup-handlers.ts"),
"utf8",
);
describe("reminder-worker — coach-message-cleanup wiring", () => {
it("imports the cleanup handler from the cleanup-handlers module", () => {
expect(workerSource).toMatch(/\bhandleCoachMessageCleanup\b/);
expect(workerSource).toMatch(/\bCoachMessageCleanupPayload\b/);
describe("coach-message-cleanup registration", () => {
it("declares the queue in the maintenance group", () => {
expect(groupOf(QUEUE).name).toBe("maintenance");
});
it("registers the coach-message-cleanup queue in the allQueues loop", () => {
const allQueuesMatch = workerSource.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bCOACH_MESSAGE_CLEANUP_QUEUE\b/);
it("schedules the nightly cron with the cron as the retry", () => {
const job = jobOf(QUEUE);
expect(job.cron).toBe("50 3 * * *");
expect(job.scheduleOptions).toBe(cronIsTheRetry);
expect(job.work).toEqual({ localConcurrency: 1 });
});
it("schedules the coach-message-cleanup cron", () => {
expect(workerSource).toMatch(
/\[COACH_MESSAGE_CLEANUP_QUEUE,\s*COACH_MESSAGE_CLEANUP_CRON,\s*cronIsTheRetry\]/,
);
});
it("registers a createAndWork handler for the queue", () => {
expect(workerSource).toMatch(
/createAndWork[\s\S]{0,200}COACH_MESSAGE_CLEANUP_QUEUE[\s\S]{0,200}handleCoachMessageCleanup/,
);
it("binds the cleanup handler", async () => {
expect(await loadedHandler(QUEUE)).toBe(handleCoachMessageCleanup);
});
it("prunes coach messages inside the handler", () => {
expect(workerSource).toMatch(
expect(handlerSource).toMatch(
/handleCoachMessageCleanup[\s\S]{0,400}cleanupOldCoachMessages/,
);
});
@@ -1,48 +1,46 @@
/**
* v1.30.3 — cumulative-PersonalRecord rederivation queue
* registration guard.
* Cumulative-PersonalRecord rederivation queue registration guard.
*
* Same source-text-grep approach as the step-consolidation-repair guard:
* assert the queue is registered in `allQueues`, a `createAndWork` handler is
* wired against it, and the boot-discovery enqueue helper fires — without
* booting pg-boss + Prisma. An unregistered queue silently never drains
* (the v1.4.37 dead-queue class).
* Reads the worker registry: the queue is declared in the rollup group,
* bound to the rederivation handler, and offered again by the rollup boot
* discovery. An unregistered queue silently never drains (the dead-queue
* class).
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
const REGISTRAR_PATH = join(__dirname, "..", "reminder", "register-rollup.ts");
const source = readFileSync(REGISTRAR_PATH, "utf8");
import { handleCumulativePrRederive } from "@/lib/jobs/reminder/rollup-handlers";
import {
discoveryOf,
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import {
CUMULATIVE_PR_REDERIVE_CONCURRENCY,
CUMULATIVE_PR_REDERIVE_QUEUE,
} from "@/lib/personal-records/cumulative-pr-rederivation";
describe("reminder-worker — cumulative-pr-rederive wiring", () => {
it("imports the queue symbols from the rederivation module", () => {
expect(source).toMatch(
/from\s*["']@\/lib\/personal-records\/cumulative-pr-rederivation["']/,
);
expect(source).toMatch(/\bCUMULATIVE_PR_REDERIVE_QUEUE\b/);
expect(source).toMatch(/\brunCumulativePrRederivationForUser\b/);
expect(source).toMatch(/\benqueueBootTimeCumulativePrRederivation\b/);
describe("cumulative-pr-rederive registration", () => {
it("declares the queue in the rollup group", () => {
expect(groupOf(CUMULATIVE_PR_REDERIVE_QUEUE).name).toBe("rollup");
});
it("registers the queue in the allQueues loop", () => {
const allQueuesMatch = source.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bCUMULATIVE_PR_REDERIVE_QUEUE\b/);
it("is a per-user exclusive one-shot at the module's concurrency", () => {
const job = jobOf(CUMULATIVE_PR_REDERIVE_QUEUE);
expect(job.policy?.policy).toBe("exclusive");
expect(job.work).toEqual({
localConcurrency: CUMULATIVE_PR_REDERIVE_CONCURRENCY,
});
expect(job.cron).toBeUndefined();
});
it("registers a createAndWork handler against the queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}CUMULATIVE_PR_REDERIVE_QUEUE[\s\S]{0,400}runCumulativePrRederivationForUser/,
it("binds the rederivation handler", async () => {
expect(await loadedHandler(CUMULATIVE_PR_REDERIVE_QUEUE)).toBe(
handleCumulativePrRederive,
);
});
it("fires the boot-discovery enqueue helper", () => {
expect(source).toMatch(
/await enqueueBootTimeCumulativePrRederivation\([^)]*\)/,
);
it("runs the boot discovery pass", () => {
expect(discoveryOf("rollup")).toContain("cumulative-pr-rederive");
});
});
@@ -1,67 +1,55 @@
/**
* v1.15 — cycle-reminder queue registration guard.
* Cycle-reminder queue registration guard.
*
* Same source-text-grep approach as the mood-reminder / drain / mean-
* consolidation registration guards: assert the cycle-reminder queue is
* declared, listed in `allQueues` (the v1.4.37 dead-queue lesson — an
* unregistered queue is never provisioned and the schedule silently
* no-ops), scheduled on its cron, and wired to a `createAndWork` handler that
* delegates to `runCycleReminderTick`. No pg-boss / Prisma boot.
* Reads the worker registry: the cycle-reminder queue is declared in the
* reminders group (an unregistered queue is never provisioned and the
* schedule silently no-ops), scheduled on its 15-minute cron, and bound to
* the handler that delegates to `runCycleReminderTick`. No pg-boss / Prisma
* boot.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the reminder-dispatch wiring moved out of the 2143-LOC
// reminder-worker boot file into the reminders registrar. The dead-queue guard
// follows the wiring there (the handler module is still concatenated for the
// handler-symbol assertions).
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-reminders.ts",
import { handleCycleReminderCheck } from "@/lib/jobs/reminder/mood-cycle-checks";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const CYCLE_REMINDER_QUEUE = "cycle-reminder-check";
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "mood-cycle-checks.ts"),
"utf8",
);
const source =
readFileSync(REGISTRAR_PATH, "utf8") +
readFileSync(
join(__dirname, "..", "reminder", "mood-cycle-checks.ts"),
"utf8",
);
describe("reminder-worker — cycle-reminder wiring", () => {
describe("cycle-reminder registration", () => {
it("imports the cycle-reminder tick runner", () => {
expect(source).toMatch(/from\s*["']@\/lib\/jobs\/cycle-reminder["']/);
expect(source).toMatch(/\brunCycleReminderTick\b/);
expect(handlerSource).toMatch(
/from\s*["']@\/lib\/jobs\/cycle-reminder["']/,
);
expect(handlerSource).toMatch(/\brunCycleReminderTick\b/);
});
it("declares the queue + cron constants", () => {
expect(source).toMatch(
/CYCLE_REMINDER_QUEUE\s*=\s*["']cycle-reminder-check["']/,
);
expect(source).toMatch(
/CYCLE_REMINDER_CRON\s*=\s*["']\*\/15 \* \* \* \*["']/,
);
it("declares the cycle-reminder queue in the reminders group", () => {
expect(groupOf(CYCLE_REMINDER_QUEUE).name).toBe("reminders");
});
it("registers the cycle-reminder queue in the allQueues loop", () => {
const allQueuesMatch = source.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bCYCLE_REMINDER_QUEUE\b/);
it("schedules the cycle-reminder cron, single-flight", () => {
const job = jobOf(CYCLE_REMINDER_QUEUE);
expect(job.cron).toBe("*/15 * * * *");
expect(job.scheduleOptions).toBeUndefined();
expect(job.work).toEqual({ localConcurrency: 1 });
});
it("schedules the cycle-reminder cron", () => {
expect(source).toMatch(/\[CYCLE_REMINDER_QUEUE,\s*CYCLE_REMINDER_CRON\]/);
});
it("registers a createAndWork handler that runs the cycle-reminder tick", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}CYCLE_REMINDER_QUEUE[\s\S]{0,200}handleCycleReminderCheck/,
it("binds the handler that runs the cycle-reminder tick", async () => {
expect(await loadedHandler(CYCLE_REMINDER_QUEUE)).toBe(
handleCycleReminderCheck,
);
expect(source).toMatch(
expect(handlerSource).toMatch(
/handleCycleReminderCheck[\s\S]{0,400}runCycleReminderTick/,
);
});
+65 -31
View File
@@ -1,19 +1,47 @@
/**
* Daily-briefing fallback cron: tz slot-gating + result accounting, plus
* the dead-queue wiring guard (imported / in allQueues / scheduled / worked).
* the dead-queue wiring guard read off the worker registry (declared,
* scheduled, bound) and the morning push the finalised refresh fires.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, it, expect, vi } from "vitest";
const { runMorningDigestRefresh, maybeDispatchPush } = vi.hoisted(() => ({
runMorningDigestRefresh: vi.fn(),
maybeDispatchPush: vi.fn(async () => "sent"),
}));
vi.mock("@/lib/jobs/morning-digest-refresh", async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/jobs/morning-digest-refresh")
>()),
runMorningDigestRefresh,
}));
vi.mock("@/lib/daily/daily-briefing-push", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/daily/daily-briefing-push")>()),
maybeDispatchDailyBriefing: maybeDispatchPush,
}));
vi.mock("@/lib/jobs/reminder/shared", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/jobs/reminder/shared")>()),
getWorkerPrisma: () => ({}),
}));
import type { PrismaClient } from "@/generated/prisma/client";
import { runDailyBriefingTick } from "@/lib/jobs/daily-briefing";
import {
runDailyBriefingTick,
DAILY_BRIEFING_QUEUE,
DAILY_BRIEFING_CRON,
} from "@/lib/jobs/daily-briefing";
} from "@/lib/jobs/daily-briefing-queue";
import type { maybeDispatchDailyBriefing } from "@/lib/daily/daily-briefing-push";
import type { MorningDigestRefreshPayload } from "@/lib/jobs/morning-digest-refresh-shared";
import {
handleDailyBriefing,
handleMorningDigestRefresh,
} from "@/lib/jobs/reminder/status-handlers";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import type { Job } from "pg-boss";
// 06:00Z → 08:00 Berlin (the fallback hour) but 02:00 in New York (not the
// slot), so a mixed-tz cohort exercises the per-user local-hour gate.
@@ -76,38 +104,44 @@ describe("runDailyBriefingTick — slot gating", () => {
});
});
describe("reminder-worker — daily-briefing wiring", () => {
const registrar = readFileSync(
join(__dirname, "..", "reminder", "register-status.ts"),
"utf8",
);
describe("daily-briefing registration", () => {
function refreshJob(): Job<MorningDigestRefreshPayload> {
return {
id: "j1",
name: "morning-digest-refresh",
data: { userId: "u1", localDate: "2026-07-16" },
expireInSeconds: 60,
heartbeatSeconds: null,
retryCount: 0,
signal: new AbortController().signal,
};
}
it("imports the queue symbols from the daily-briefing module", () => {
expect(registrar).toMatch(/from\s*["']@\/lib\/jobs\/daily-briefing["']/);
expect(registrar).toMatch(/\bDAILY_BRIEFING_QUEUE\b/);
expect(registrar).toMatch(/\bDAILY_BRIEFING_CRON\b/);
expect(registrar).toMatch(/\brunDailyBriefingTick\b/);
it("declares the queue in the status group and schedules the cron", () => {
expect(groupOf(DAILY_BRIEFING_QUEUE).name).toBe("status");
const job = jobOf(DAILY_BRIEFING_QUEUE);
expect(job.cron).toBe(DAILY_BRIEFING_CRON);
expect(job.work).toEqual({ localConcurrency: 1 });
});
it("registers the queue in allQueues and schedules the cron", () => {
const allQueues = registrar.match(/const allQueues\s*=\s*\[([\s\S]*?)\];/);
expect(allQueues).not.toBeNull();
expect(allQueues![1]).toMatch(/\bDAILY_BRIEFING_QUEUE\b/);
expect(registrar).toMatch(
/\[DAILY_BRIEFING_QUEUE,\s*DAILY_BRIEFING_CRON\]/,
);
it("binds the handler that runs the tick", async () => {
expect(await loadedHandler(DAILY_BRIEFING_QUEUE)).toBe(handleDailyBriefing);
});
it("wires a createAndWork handler that runs the tick", () => {
expect(registrar).toMatch(
/createAndWork[\s\S]{0,200}DAILY_BRIEFING_QUEUE[\s\S]{0,400}runDailyBriefingTick/,
it("fires the finalisation-hook push after a finalised morning refresh", async () => {
runMorningDigestRefresh.mockResolvedValueOnce({ status: "finalised" });
await handleMorningDigestRefresh([refreshJob()]);
expect(maybeDispatchPush).toHaveBeenCalledTimes(1);
expect(maybeDispatchPush).toHaveBeenCalledWith(
expect.anything(),
"u1",
expect.any(Date),
);
});
it("fires the finalisation-hook push after a finalised morning refresh", () => {
expect(registrar).toMatch(
/result\.status\s*===\s*["']finalised["'][\s\S]{0,300}maybeDispatchDailyBriefing/,
);
maybeDispatchPush.mockClear();
runMorningDigestRefresh.mockResolvedValueOnce({ status: "already-final" });
await handleMorningDigestRefresh([refreshJob()]);
expect(maybeDispatchPush).not.toHaveBeenCalled();
});
it("uses an every-15-min cron for the fallback slot", () => {
@@ -1,5 +1,5 @@
/**
* v1.4.37 dead-queue guard for the `data-arrival` queue.
* Dead-queue guard for the `data-arrival` queue.
*
* Four facts have to hold together or the spine is silently inert: the queue
* name is provisioned, a handler drains it, the queue carries a policy that
@@ -7,83 +7,65 @@
* is itself registered. Any one of them missing fails open in the worst way —
* the emits succeed, the jobs vanish, and nothing anywhere reports a problem.
*
* Source-text assertions over the registrar modules, matching the shape of the
* sibling `*-queue.test.ts` guards.
* Reads the worker registry for the queue facts; the retention handler's body
* is read as text.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
const statusRegistrar = readFileSync(
join(__dirname, "..", "reminder", "register-status.ts"),
import { DATA_ARRIVAL_QUEUE } from "@/lib/arrivals/emit-shared";
import { handleDataArrival } from "@/lib/jobs/data-arrival";
import { handleArrivalReactionCleanup } from "@/lib/jobs/reminder/cleanup-handlers";
import { cronIsTheRetry } from "@/lib/jobs/reminder/registrar-shared";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const ARRIVAL_REACTION_CLEANUP_QUEUE = "arrival-reaction-cleanup";
const cleanupHandlers = readFileSync(
join(__dirname, "..", "reminder", "cleanup-handlers.ts"),
"utf8",
);
const maintenanceSource =
readFileSync(
join(__dirname, "..", "reminder", "register-maintenance.ts"),
"utf8",
) +
readFileSync(
join(__dirname, "..", "reminder", "cleanup-handlers.ts"),
"utf8",
);
describe("data-arrival queue wiring", () => {
it("provisions the queue in the allQueues list", () => {
const allQueues = statusRegistrar.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueues).not.toBeNull();
expect(allQueues![1]).toMatch(/\bDATA_ARRIVAL_QUEUE\b/);
it("provisions the queue in the status group", () => {
expect(groupOf(DATA_ARRIVAL_QUEUE).name).toBe("status");
});
it("binds a createAndWork handler that drains it", () => {
expect(statusRegistrar).toMatch(
/createAndWork[\s\S]{0,200}DATA_ARRIVAL_QUEUE[\s\S]{0,200}handleDataArrival/,
);
it("binds the handler that drains it", async () => {
expect(await loadedHandler(DATA_ARRIVAL_QUEUE)).toBe(handleDataArrival);
});
it("carries the exclusive policy its singleton keys depend on", () => {
// Under pg-boss's default `standard` policy NO index covers
// `singleton_key`, so the day-scoped keys would coalesce nothing at all and
// a chatty ingest would queue one job per batch. This assertion is anchored
// INSIDE the extracted policy table so an unrelated mention of the queue
// name elsewhere in the file cannot satisfy it.
const table = statusRegistrar.match(
/const queuePolicies: QueuePolicyTable\s*=\s*\{([\s\S]*?)\n\};/,
);
expect(table).not.toBeNull();
expect(table![1]).toMatch(
/\[DATA_ARRIVAL_QUEUE\][\s\S]{0,200}policy:\s*"exclusive"/,
);
// a chatty ingest would queue one job per batch.
expect(jobOf(DATA_ARRIVAL_QUEUE).policy?.policy).toBe("exclusive");
});
it("has no cron schedule — it is send-only, driven by ingest", () => {
// Tolerates the optional third schedule element, so a cron sneaking in
// with send options still trips this.
expect(statusRegistrar).not.toMatch(
/\[\s*DATA_ARRIVAL_QUEUE,\s*\w+_CRON\s*[,\]]/,
);
const job = jobOf(DATA_ARRIVAL_QUEUE);
expect(job.cron).toBeUndefined();
expect(job.scheduleOptions).toBeUndefined();
});
it("registers the retention job that prunes the markers", () => {
const allQueues = maintenanceSource.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueues).not.toBeNull();
expect(allQueues![1]).toMatch(/\bARRIVAL_REACTION_CLEANUP_QUEUE\b/);
expect(maintenanceSource).toMatch(
/\[\s*ARRIVAL_REACTION_CLEANUP_QUEUE,\s*ARRIVAL_REACTION_CLEANUP_CRON,\s*cronIsTheRetry,?\s*\]/,
);
expect(maintenanceSource).toMatch(
/createAndWork[\s\S]{0,200}ARRIVAL_REACTION_CLEANUP_QUEUE[\s\S]{0,200}handleArrivalReactionCleanup/,
it("registers the retention job that prunes the markers", async () => {
expect(groupOf(ARRIVAL_REACTION_CLEANUP_QUEUE).name).toBe("maintenance");
const job = jobOf(ARRIVAL_REACTION_CLEANUP_QUEUE);
expect(job.cron).toBe("10 4 * * *");
expect(job.scheduleOptions).toBe(cronIsTheRetry);
expect(await loadedHandler(ARRIVAL_REACTION_CLEANUP_QUEUE)).toBe(
handleArrivalReactionCleanup,
);
});
it("the retention handler actually deletes arrival reactions by age", () => {
expect(maintenanceSource).toMatch(
expect(cleanupHandlers).toMatch(
/handleArrivalReactionCleanup[\s\S]{0,600}arrivalReaction\.deleteMany/,
);
});
@@ -29,7 +29,8 @@ import {
import {
OFFHOST_BACKUP_QUEUE,
OFFHOST_BACKUP_SEND_OPTIONS,
} from "../offhost-backup";
} from "@/lib/jobs/offhost-backup-queue";
import { jobOf } from "../registry/__tests__/registry-helpers";
const read = (file: string) => readFileSync(join(process.cwd(), file), "utf8");
@@ -59,13 +60,12 @@ describe("data-backup — pg-boss send policy", () => {
});
it("the weekly and nightly schedules carry the policy", () => {
const source = read("src/lib/jobs/reminder/register-maintenance.ts");
expect(source).toMatch(
/\[\s*DATA_BACKUP_QUEUE,\s*DATA_BACKUP_CRON,\s*DATA_BACKUP_SEND_OPTIONS,?\s*\]/,
);
expect(source).toMatch(
/\[\s*OFFHOST_BACKUP_QUEUE,\s*OFFHOST_BACKUP_CRON,\s*OFFHOST_BACKUP_SEND_OPTIONS,?\s*\]/,
);
const weekly = jobOf(DATA_BACKUP_QUEUE);
expect(weekly.cron).toBe("0 3 * * 0");
expect(weekly.scheduleOptions).toBe(DATA_BACKUP_SEND_OPTIONS);
const nightly = jobOf(OFFHOST_BACKUP_QUEUE);
expect(nightly.cron).toBe("30 2 * * *");
expect(nightly.scheduleOptions).toBe(OFFHOST_BACKUP_SEND_OPTIONS);
});
it("the admin run-now route sends with the same policy", () => {
@@ -112,11 +112,11 @@ vi.mock("@/lib/jobs/boss-instance", () => ({
import { AiUnavailableError } from "@/lib/ai/capabilities/refusal";
import {
DOCUMENT_AI_RUN_EXPIRE_SECONDS,
DOCUMENT_AI_RUN_QUEUE,
enqueueDocumentAiRun,
handleDocumentAiRunJobs,
runDocumentAiRun,
} from "@/lib/jobs/document-ai-run";
import { DOCUMENT_AI_RUN_QUEUE } from "@/lib/jobs/document-ai-run-queue";
const NOW = 1_800_000_000_000;
const now = () => NOW;
@@ -15,11 +15,8 @@ vi.mock("@/lib/documents/auto-stage-labs", () => ({
maybeAutoStageLabFacts: vi.fn().mockResolvedValue(undefined),
}));
import {
DOCUMENT_INDEX_QUEUE,
enqueueDocumentIndex,
runDocumentIndex,
} from "../document-index";
import { enqueueDocumentIndex, runDocumentIndex } from "../document-index";
import { DOCUMENT_INDEX_QUEUE } from "../document-index-queue";
import { indexDocumentContent } from "@/lib/documents/index-document";
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import { maybeAutoStageLabFacts } from "@/lib/documents/auto-stage-labs";
@@ -32,8 +32,8 @@ import {
enqueueSummaryCatchUp,
runSummaryCatchUpForUser,
MAX_ENQUEUES_PER_RUN,
DOCUMENT_SUMMARY_CATCHUP_QUEUE,
} from "../document-summary-catchup";
import { DOCUMENT_SUMMARY_CATCHUP_QUEUE } from "../document-summary-catchup-queue";
import { prisma } from "@/lib/db";
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import { documentAutoReadEnabled } from "@/lib/documents/document-settings";
@@ -1,75 +1,136 @@
/**
* v1.4.37 — pg-boss queue registration for the nightly drain of
* per-sample APPLE_HEALTH cumulative rows. The drain helper exists
* since v1.4.30 but only ran via the admin endpoint / CLI; the
* scheduled wrapper closes the loop so the list view stops painting
* hundreds of step chunks per day.
* pg-boss queue registration for the nightly drain of per-sample
* APPLE_HEALTH cumulative rows. The drain helper once ran only via the admin
* endpoint / CLI; the scheduled wrapper closes the loop so the list view
* stops painting hundreds of step chunks per day.
*
* Same source-text-grep approach as the Withings queue regression
* guard so we never have to boot pg-boss + Prisma to assert the
* scheduler wiring stays intact.
* Reads the worker registry (the queue, its cron and its binding) and runs
* the bound handler against mocked passes, so pg-boss and Prisma never boot.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
const drainPerSampleCumulative = vi.hoisted(() =>
vi.fn(async () => ({
stoppedEarly: false,
totals: {
usersScanned: 0,
bucketsCollapsed: 0,
perSampleRowsDeleted: 0,
dailyRowsUpserted: 0,
},
})),
);
vi.mock(
"@/lib/measurements/drain-per-sample-cumulative",
async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/measurements/drain-per-sample-cumulative")
>()),
drainPerSampleCumulative,
}),
);
vi.mock(
"@/lib/measurements/consolidate-daily-mean",
async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/measurements/consolidate-daily-mean")
>()),
consolidateDailyMean: vi.fn(async () => ({
stoppedEarly: false,
totals: {
usersScanned: 0,
daysConsolidated: 0,
perSampleRowsSoftDeleted: 0,
dailyRowsUpserted: 0,
daysFailed: 0,
},
})),
}),
);
vi.mock(
"@/lib/measurements/dense-intraday-retention",
async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/measurements/dense-intraday-retention")
>()),
runDenseIntradayRetention: vi.fn(async () => ({
stoppedEarly: false,
totals: {
usersScanned: 0,
daysConsolidated: 0,
perSampleRowsSoftDeleted: 0,
hourlyRowsUpserted: 0,
dailyRowsRetired: 0,
},
})),
}),
);
vi.mock("@/lib/jobs/reminder/shared", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/jobs/reminder/shared")>()),
getWorkerPrisma: () => ({}),
}));
// v1.18.1 — the drain-cumulative wiring moved out of the 2143-LOC
// reminder-worker boot file into the rollup registrar. The dead-queue guard
// follows the wiring there.
const REGISTRAR_PATH = join(__dirname, "..", "reminder", "register-rollup.ts");
const source = readFileSync(REGISTRAR_PATH, "utf8");
import {
DRAIN_CUMULATIVE_CRON,
DRAIN_CUMULATIVE_QUEUE,
} from "@/lib/jobs/drain-cumulative-queue";
import { cronIsTheRetry } from "@/lib/jobs/reminder/registrar-shared";
import {
groupOf,
jobOf,
recordingBoss,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import { DRAIN_CUMULATIVE_CUTOFF_HOURS } from "@/lib/measurements/drain-per-sample-cumulative";
describe("reminder-worker — drainPerSampleCumulative nightly schedule", () => {
describe("drainPerSampleCumulative nightly schedule", () => {
it("declares the drain queue at the documented Berlin cadence", () => {
expect(source).toMatch(
/DRAIN_CUMULATIVE_QUEUE\s*=\s*["']drain-per-sample-cumulative["']/,
);
expect(DRAIN_CUMULATIVE_QUEUE).toBe("drain-per-sample-cumulative");
// 03:45 Europe/Berlin — between audit-log cleanup (03:15) and the
// feedback aggregator (04:00). Keep the slot stable so an operator
// staring at the cron table can find the job easily.
expect(source).toMatch(/DRAIN_CUMULATIVE_CRON\s*=\s*["']45 3 \* \* \*["']/);
expect(DRAIN_CUMULATIVE_CRON).toBe("45 3 * * *");
});
it("passes the 36 hour grace window into the drain helper", () => {
// v1.4.38 — the literal `36` lives in the helper module so the
// worker, the admin route, and the CLI all read one source of
// truth. The worker still references the constant by name in the
// cutoffHours slot so the cron's behaviour is unchanged.
expect(source).toMatch(/cutoffHours:\s*DRAIN_CUMULATIVE_CUTOFF_HOURS/);
});
it("imports the cutoff constant and the helper from the drain module", () => {
// v1.4.38 — the import is now a multi-symbol form. Match the helper
// and the constant separately so a future contributor who adds a
// third symbol does not break the assertion.
expect(source).toMatch(
/from\s*["']@\/lib\/measurements\/drain-per-sample-cumulative["']/,
it("passes the 36 hour grace window into the drain helper", async () => {
// The literal `36` lives in the helper module so the worker, the admin
// route, and the CLI all read one source of truth.
expect(DRAIN_CUMULATIVE_CUTOFF_HOURS).toBe(36);
const { boss, sends } = recordingBoss();
const handler = await jobOf(DRAIN_CUMULATIVE_QUEUE).load({ boss });
await handler([
{
id: "j1",
name: DRAIN_CUMULATIVE_QUEUE,
data: { triggeredAt: "2026-01-01T03:45:00.000Z" },
expireInSeconds: 900,
heartbeatSeconds: null,
retryCount: 0,
signal: new AbortController().signal,
},
]);
expect(drainPerSampleCumulative).toHaveBeenCalledTimes(1);
expect(drainPerSampleCumulative).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({
dryRun: false,
cutoffHours: DRAIN_CUMULATIVE_CUTOFF_HOURS,
}),
);
expect(source).toMatch(/\bdrainPerSampleCumulative\b/);
expect(source).toMatch(/\bDRAIN_CUMULATIVE_CUTOFF_HOURS\b/);
// A tick that finished inside its budget hands nothing on.
expect(sends).toEqual([]);
});
it("registers a createAndWork handler against the drain queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}DRAIN_CUMULATIVE_QUEUE[\s\S]{0,1100}drainPerSampleCumulative/,
);
it("schedules the drain cron with the cron as the retry", () => {
const job = jobOf(DRAIN_CUMULATIVE_QUEUE);
expect(job.cron).toBe(DRAIN_CUMULATIVE_CRON);
expect(job.scheduleOptions).toBe(cronIsTheRetry);
expect(job.work).toEqual({ localConcurrency: 1 });
});
it("schedules the drain cron via boss.schedule (allQueues + schedules)", () => {
expect(source).toMatch(
/\[DRAIN_CUMULATIVE_QUEUE,\s*DRAIN_CUMULATIVE_CRON,\s*cronIsTheRetry\]/,
);
});
it("registers the drain queue in the allQueues createQueue loop", () => {
// pg-boss v12 requires explicit createQueue before scheduling. The
// allQueues array drives the boot-time `for (const q of allQueues)`
// loop; missing the entry silently no-ops the schedule below.
const allQueuesMatch = source.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bDRAIN_CUMULATIVE_QUEUE\b/);
it("declares the drain queue in the rollup group", () => {
// pg-boss v12 requires explicit createQueue before scheduling; a queue
// the registry does not declare is never created and its schedule
// silently no-ops.
expect(groupOf(DRAIN_CUMULATIVE_QUEUE).name).toBe("rollup");
});
});
+86 -66
View File
@@ -1,12 +1,12 @@
/**
* v1.12.0 — pg-boss queue registration for the Fitbit / Google Health sync layer.
* pg-boss queue registration for the Fitbit sync layer.
*
* The reminder-worker imports heavy infrastructure (pg-boss, Prisma adapter,
* notification dispatchers, …) so we don't boot the whole worker here. Instead we
* read the source as text and assert the queue constants + cron schedules +
* handler wiring + allQueues + boot-enqueue are present — the fast guard that
* catches the v1.4.37 dead-queue class (a queue declared but never registered in
* `allQueues`, which silently never drains).
* Reads the worker registry instead of booting the worker: every Fitbit queue
* is declared in the integration-sync group with its cron and handler, the
* full-history queues forward to the shared admission lane, and the boot
* discovery offers the backfill and the sleep repair again. This is the fast
* guard for the dead-queue class (a queue declared but never registered,
* which silently never drains).
*
* Fitbit is poll-only at launch (no webhook — Pub/Sub deferred), so a single
* hourly `fitbit-sync` queue drives the per-user `syncUserFitbit` driver across
@@ -17,81 +17,101 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the queue wiring + boot discovery moved out of the 2143-LOC
// reminder-worker boot file into domain registrars; the Fitbit queues live in
// the integration-sync registrar. The dead-queue guard follows the wiring there
// (the handler module is still concatenated for the import-routine assertions).
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-integration-sync.ts",
);
const source =
readFileSync(REGISTRAR_PATH, "utf8") +
readFileSync(join(__dirname, "..", "reminder", "fitbit-sync.ts"), "utf8");
import {
FITBIT_BACKFILL_QUEUE,
FITBIT_OAUTH_STATE_CLEANUP_CRON,
FITBIT_OAUTH_STATE_CLEANUP_QUEUE,
FITBIT_SLEEP_REPAIR_QUEUE,
FITBIT_SYNC_CRON,
FITBIT_SYNC_QUEUE,
} from "@/lib/fitbit/jobs/queues";
import { INTEGRATION_BACKFILL_ADMISSION_QUEUE } from "@/lib/jobs/integration-backfill-admission";
import {
handleFitbitOAuthStateCleanup,
handleFitbitSync,
} from "@/lib/jobs/reminder/fitbit-sync";
import { cronIsTheRetry } from "@/lib/jobs/reminder/registrar-shared";
import {
discoveryOf,
groupOf,
jobOf,
loadedHandler,
recordingBoss,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const FITBIT_QUEUE_CONSTS = [
"FITBIT_SYNC_QUEUE",
"FITBIT_BACKFILL_QUEUE",
"FITBIT_SLEEP_REPAIR_QUEUE",
"FITBIT_OAUTH_STATE_CLEANUP_QUEUE",
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "fitbit-sync.ts"),
"utf8",
);
const FITBIT_QUEUES = [
FITBIT_SYNC_QUEUE,
FITBIT_BACKFILL_QUEUE,
FITBIT_SLEEP_REPAIR_QUEUE,
FITBIT_OAUTH_STATE_CLEANUP_QUEUE,
] as const;
describe("reminder-worker — Fitbit sync queues", () => {
/** The admission sends the source queue's handler makes for one job. */
async function forwardOneJob(queue: string) {
const { boss, sends } = recordingBoss();
const handler = await jobOf(queue).load({ boss });
await handler([
{
id: "j1",
name: queue,
data: { userId: "u1" },
expireInSeconds: 60,
heartbeatSeconds: null,
retryCount: 0,
signal: new AbortController().signal,
},
]);
return sends;
}
describe("Fitbit sync queues", () => {
it("declares the Fitbit sync + oauth-state-cleanup queue constants", () => {
expect(source).toMatch(/FITBIT_SYNC_QUEUE\s*=\s*["']fitbit-sync["']/);
expect(source).toMatch(
/FITBIT_OAUTH_STATE_CLEANUP_QUEUE\s*=\s*["']fitbit-oauth-state-cleanup["']/,
);
expect(FITBIT_SYNC_QUEUE).toBe("fitbit-sync");
expect(FITBIT_OAUTH_STATE_CLEANUP_QUEUE).toBe("fitbit-oauth-state-cleanup");
});
it("registers EVERY Fitbit queue in allQueues (v1.4.37 dead-queue guard)", () => {
const block = /const allQueues = \[([\s\S]*?)\];/.exec(source);
expect(block).not.toBeNull();
const body = block![1]!;
for (const q of FITBIT_QUEUE_CONSTS) {
expect(body).toContain(q);
it("declares EVERY Fitbit queue in the integration-sync group", () => {
for (const q of FITBIT_QUEUES) {
expect(groupOf(q).name).toBe("integration_sync");
}
});
it("schedules the hourly poll cron + the daily oauth-state sweep", () => {
expect(source).toMatch(/\[FITBIT_SYNC_QUEUE,\s*FITBIT_SYNC_CRON\]/);
expect(source).toMatch(
/\[\s*FITBIT_OAUTH_STATE_CLEANUP_QUEUE,\s*FITBIT_OAUTH_STATE_CLEANUP_CRON,\s*cronIsTheRetry,?\s*\]/,
);
expect(jobOf(FITBIT_SYNC_QUEUE).cron).toBe(FITBIT_SYNC_CRON);
expect(jobOf(FITBIT_SYNC_QUEUE).scheduleOptions).toBeUndefined();
const sweep = jobOf(FITBIT_OAUTH_STATE_CLEANUP_QUEUE);
expect(sweep.cron).toBe(FITBIT_OAUTH_STATE_CLEANUP_CRON);
expect(sweep.scheduleOptions).toBe(cronIsTheRetry);
});
it("registers a createAndWork handler for every Fitbit queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,160}FITBIT_SYNC_QUEUE[\s\S]{0,160}handleFitbitSync/,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,200}FITBIT_BACKFILL_QUEUE[\s\S]{0,260}enqueueIntegrationBackfillAdmission/,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,200}FITBIT_SLEEP_REPAIR_QUEUE[\s\S]{0,260}enqueueIntegrationBackfillAdmission/,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,160}FITBIT_OAUTH_STATE_CLEANUP_QUEUE[\s\S]{0,160}handleFitbitOAuthStateCleanup/,
it("binds a handler for every Fitbit queue", async () => {
expect(await loadedHandler(FITBIT_SYNC_QUEUE)).toBe(handleFitbitSync);
expect(await loadedHandler(FITBIT_OAUTH_STATE_CLEANUP_QUEUE)).toBe(
handleFitbitOAuthStateCleanup,
);
for (const [queue, kind] of [
[FITBIT_BACKFILL_QUEUE, "fitbit-backfill"],
[FITBIT_SLEEP_REPAIR_QUEUE, "fitbit-sleep-repair"],
] as const) {
const sends = await forwardOneJob(queue);
expect(sends).toHaveLength(1);
expect(sends[0]!.queue).toBe(INTEGRATION_BACKFILL_ADMISSION_QUEUE);
expect(sends[0]!.data).toMatchObject({ kind, data: { userId: "u1" } });
}
});
it("wires the self-converging Fitbit backfill boot discovery", () => {
expect(source).toMatch(
/enqueueBootTimeFitbitBackfill\(\s*bootStaggerSecondsFor\("fitbit-backfill"\)/,
);
it("wires the self-converging backfill and the one-shot sleep-repair boot discovery", () => {
const passes = discoveryOf("integration_sync");
expect(passes).toContain("fitbit-backfill");
expect(passes).toContain("fitbit-sleep-repair");
});
it("wires the one-shot Fitbit sleep-repair boot discovery", () => {
expect(source).toMatch(
/enqueueBootTimeFitbitSleepRepair\(\s*bootStaggerSecondsFor\("fitbit-sleep-repair"\)/,
);
expect(source).toMatch(/from\s*["']@\/lib\/jobs\/fitbit-sleep-repair["']/);
});
it("imports the Fitbit sync driver + backfill exports", () => {
expect(source).toMatch(/from\s*["']@\/lib\/fitbit\/sync["']/);
expect(source).toMatch(/from\s*["']@\/lib\/jobs\/fitbit-backfill["']/);
it("imports the Fitbit sync driver into the handler module", () => {
expect(handlerSource).toMatch(/from\s*["']@\/lib\/fitbit\/sync["']/);
});
});
+15 -20
View File
@@ -283,13 +283,13 @@ describe("runGeoBackfill", () => {
});
/**
* v1.4.37 — the helper is now scheduled via pg-boss at the hourly
* :40 slot. The schedule constants live in the helper module so the
* reminder-worker boot pulls them by import; this test pins both
* shapes so a regression that drops the cadence or drifts the queue
* name lands here, not in production.
* The helper is scheduled via pg-boss at the hourly :40 slot. The
* schedule constants live in the helper module so the worker registry
* pulls them by import; this test pins both shapes so a regression that
* drops the cadence or drifts the queue name lands here, not in
* production.
*/
describe("geo-backfill scheduling contract (v1.4.37)", () => {
describe("geo-backfill scheduling contract", () => {
it("exports the pg-boss queue name", () => {
expect(GEO_BACKFILL_QUEUE).toBe("geo-backfill");
});
@@ -298,20 +298,11 @@ describe("geo-backfill scheduling contract (v1.4.37)", () => {
expect(GEO_BACKFILL_CRON).toBe("40 * * * *");
});
it("reminder-worker imports both the runner and the schedule constants", async () => {
// Source-text probe avoids importing the worker module (which
// would drag pg-boss + a live Postgres connection). The contract
// is binary: the worker either registers the queue + cron or it
// does not; both presence checks pin the v1.4.37 scheduling
// shape end-to-end.
it("the worker registry schedules the queue and binds the ops handler", async () => {
// The handler module's import is read as text; the queue facts come from
// the registry, so neither pg-boss nor a live Postgres connection boots.
const fs = await import("node:fs/promises");
const path = await import("node:path");
// v1.18.1 — the geo-backfill wiring moved out of the 2143-LOC
// reminder-worker boot file into the maintenance registrar.
const workerSrc = await fs.readFile(
path.resolve(__dirname, "..", "reminder", "register-maintenance.ts"),
"utf-8",
);
const handlerSrc = await fs.readFile(
path.resolve(__dirname, "..", "reminder", "ops-handlers.ts"),
"utf-8",
@@ -319,7 +310,11 @@ describe("geo-backfill scheduling contract (v1.4.37)", () => {
expect(handlerSrc).toMatch(
/import \{[^}]*runGeoBackfill[^}]*\} from "@\/lib\/jobs\/geo-backfill";/,
);
expect(workerSrc).toContain("GEO_BACKFILL_QUEUE, GEO_BACKFILL_CRON");
expect(workerSrc).toContain("handleGeoBackfill");
const { groupOf, jobOf, loadedHandler } =
await import("../registry/__tests__/registry-helpers");
const { handleGeoBackfill } = await import("../reminder/ops-handlers");
expect(groupOf(GEO_BACKFILL_QUEUE).name).toBe("maintenance");
expect(jobOf(GEO_BACKFILL_QUEUE).cron).toBe(GEO_BACKFILL_CRON);
expect(await loadedHandler(GEO_BACKFILL_QUEUE)).toBe(handleGeoBackfill);
});
});
@@ -1,10 +1,10 @@
/**
* Runtime GeoLite2 fetch (issue #659) — queue-wiring guard + boot gating.
*
* The source-text-grep half mirrors the other dead-queue guards: assert the
* queue is imported, in `allQueues`, scheduled, and bound to a `createAndWork`
* handler that runs the fetch — without booting pg-boss. An unregistered queue
* silently never drains (the recurring v1.4.37 dead-queue bug), which here
* The first half reads the worker registry: the queue is declared in the
* maintenance group, scheduled monthly, and bound to the handler that runs
* the fetch, and the maintenance boot discovery carries the fetch pass. An
* unregistered queue silently never drains (the dead-queue class), which here
* would mean a keyed self-host never gets its databases fetched.
*
* The second half exercises the boot-discovery gate directly: it must send a
@@ -15,54 +15,46 @@ import { readFileSync } from "node:fs";
import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-maintenance.ts",
import {
GEOLITE2_FETCH_CRON,
GEOLITE2_FETCH_QUEUE,
} from "@/lib/jobs/geolite2-fetch";
import { handleGeolite2Fetch } from "@/lib/jobs/reminder/ops-handlers";
import {
discoveryOf,
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "ops-handlers.ts"),
"utf8",
);
const workerSource =
readFileSync(REGISTRAR_PATH, "utf8") +
readFileSync(join(__dirname, "..", "reminder", "ops-handlers.ts"), "utf8");
describe("reminder-worker — geolite2-fetch wiring", () => {
it("imports the queue symbols from the geolite2-fetch module", () => {
expect(workerSource).toMatch(/from\s*["']@\/lib\/jobs\/geolite2-fetch["']/);
expect(workerSource).toMatch(/\bGEOLITE2_FETCH_QUEUE\b/);
expect(workerSource).toMatch(/\bGEOLITE2_FETCH_CRON\b/);
expect(workerSource).toMatch(/\benqueueGeolite2FetchBootDiscovery\b/);
describe("geolite2-fetch registration", () => {
it("declares the queue in the maintenance group", () => {
expect(groupOf(GEOLITE2_FETCH_QUEUE).name).toBe("maintenance");
});
it("registers the queue in the allQueues loop", () => {
const allQueuesMatch = workerSource.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bGEOLITE2_FETCH_QUEUE\b/);
it("schedules the monthly cron with the default retries", () => {
const job = jobOf(GEOLITE2_FETCH_QUEUE);
expect(job.cron).toBe(GEOLITE2_FETCH_CRON);
expect(job.scheduleOptions).toBeUndefined();
});
it("schedules the monthly cron", () => {
expect(workerSource).toMatch(
/\[GEOLITE2_FETCH_QUEUE,\s*GEOLITE2_FETCH_CRON\]/,
);
});
it("registers a createAndWork handler for the queue", () => {
expect(workerSource).toMatch(
/createAndWork[\s\S]{0,200}GEOLITE2_FETCH_QUEUE[\s\S]{0,200}handleGeolite2Fetch/,
);
it("binds the fetch handler", async () => {
expect(await loadedHandler(GEOLITE2_FETCH_QUEUE)).toBe(handleGeolite2Fetch);
});
it("runs the fetch inside the handler", () => {
expect(workerSource).toMatch(
expect(handlerSource).toMatch(
/handleGeolite2Fetch[\s\S]{0,500}fetchGeoLite2Databases/,
);
});
it("fires the boot discovery from enqueueMaintenanceBootDiscovery", () => {
expect(workerSource).toMatch(
/enqueueMaintenanceBootDiscovery[\s\S]*enqueueGeolite2FetchBootDiscovery/,
);
it("fires the boot discovery from the maintenance group", () => {
expect(discoveryOf("maintenance")).toContain("geolite2-fetch");
});
});
@@ -1,12 +1,11 @@
/**
* v1.26.0 — pg-boss queue registration for the Google Health sync layer.
* pg-boss queue registration for the Google Health sync layer.
*
* The reminder-worker imports heavy infrastructure (pg-boss, Prisma adapter,
* notification dispatchers, …) so we don't boot the whole worker here. Instead
* we read the source as text and assert the queue constants + cron schedules +
* handler wiring + allQueues + boot-enqueue are present — the fast guard that
* catches the v1.4.37 dead-queue class (a queue declared but never registered
* in `allQueues`, which silently never drains).
* Reads the worker registry instead of booting the worker: every Google
* Health queue is declared in the integration-sync group with its cron and
* handler, the backfill forwards to the shared admission lane, and the boot
* discovery offers it again. This is the fast guard for the dead-queue class
* (a queue declared but never registered, which silently never drains).
*
* Google Health is poll-only at launch (no webhook — Pub/Sub deferred), so a
* single hourly `google-health-sync` queue drives the per-user
@@ -18,75 +17,90 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-integration-sync.ts",
);
const source =
readFileSync(REGISTRAR_PATH, "utf8") +
readFileSync(
join(__dirname, "..", "reminder", "google-health-sync.ts"),
"utf8",
);
import { GOOGLE_HEALTH_BACKFILL_QUEUE } from "@/lib/jobs/google-health-backfill-queue";
import { INTEGRATION_BACKFILL_ADMISSION_QUEUE } from "@/lib/jobs/integration-backfill-admission";
import {
GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_CRON,
GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE,
GOOGLE_HEALTH_SYNC_CRON,
GOOGLE_HEALTH_SYNC_QUEUE,
} from "@/lib/jobs/integration-poll-queues";
import {
handleGoogleHealthOAuthStateCleanup,
handleGoogleHealthSync,
} from "@/lib/jobs/reminder/google-health-sync";
import { cronIsTheRetry } from "@/lib/jobs/reminder/registrar-shared";
import {
discoveryOf,
groupOf,
jobOf,
loadedHandler,
recordingBoss,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const GOOGLE_HEALTH_QUEUE_CONSTS = [
"GOOGLE_HEALTH_SYNC_QUEUE",
"GOOGLE_HEALTH_BACKFILL_QUEUE",
"GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE",
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "google-health-sync.ts"),
"utf8",
);
const GOOGLE_HEALTH_QUEUES = [
GOOGLE_HEALTH_SYNC_QUEUE,
GOOGLE_HEALTH_BACKFILL_QUEUE,
GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE,
] as const;
describe("reminder-worker — Google Health sync queues", () => {
describe("Google Health sync queues", () => {
it("declares the Google Health sync + oauth-state-cleanup queue constants", () => {
expect(source).toMatch(
/GOOGLE_HEALTH_SYNC_QUEUE\s*=\s*["']google-health-sync["']/,
);
expect(source).toMatch(
/GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE\s*=\s*["']google-health-oauth-state-cleanup["']/,
expect(GOOGLE_HEALTH_SYNC_QUEUE).toBe("google-health-sync");
expect(GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE).toBe(
"google-health-oauth-state-cleanup",
);
});
it("registers EVERY Google Health queue in allQueues (v1.4.37 dead-queue guard)", () => {
const block = /const allQueues = \[([\s\S]*?)\];/.exec(source);
expect(block).not.toBeNull();
const body = block![1]!;
for (const q of GOOGLE_HEALTH_QUEUE_CONSTS) {
expect(body).toContain(q);
it("declares EVERY Google Health queue in the integration-sync group", () => {
for (const q of GOOGLE_HEALTH_QUEUES) {
expect(groupOf(q).name).toBe("integration_sync");
}
});
it("schedules the hourly poll cron + the daily oauth-state sweep", () => {
expect(source).toMatch(
/\[GOOGLE_HEALTH_SYNC_QUEUE,\s*GOOGLE_HEALTH_SYNC_CRON\]/,
);
expect(source).toMatch(
/\[\s*GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE,\s*GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_CRON,\s*cronIsTheRetry,?\s*\]/,
);
expect(jobOf(GOOGLE_HEALTH_SYNC_QUEUE).cron).toBe(GOOGLE_HEALTH_SYNC_CRON);
expect(jobOf(GOOGLE_HEALTH_SYNC_QUEUE).scheduleOptions).toBeUndefined();
const sweep = jobOf(GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE);
expect(sweep.cron).toBe(GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_CRON);
expect(sweep.scheduleOptions).toBe(cronIsTheRetry);
});
it("registers a createAndWork handler for every Google Health queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}GOOGLE_HEALTH_SYNC_QUEUE[\s\S]{0,200}handleGoogleHealthSync/,
it("binds a handler for every Google Health queue", async () => {
expect(await loadedHandler(GOOGLE_HEALTH_SYNC_QUEUE)).toBe(
handleGoogleHealthSync,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,320}GOOGLE_HEALTH_BACKFILL_QUEUE[\s\S]{0,320}enqueueIntegrationBackfillAdmission/,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,200}GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE[\s\S]{0,200}handleGoogleHealthOAuthStateCleanup/,
expect(await loadedHandler(GOOGLE_HEALTH_OAUTH_STATE_CLEANUP_QUEUE)).toBe(
handleGoogleHealthOAuthStateCleanup,
);
const { boss, sends } = recordingBoss();
const backfill = await jobOf(GOOGLE_HEALTH_BACKFILL_QUEUE).load({ boss });
await backfill([
{
id: "j1",
name: GOOGLE_HEALTH_BACKFILL_QUEUE,
data: { userId: "u1" },
expireInSeconds: 60,
heartbeatSeconds: null,
retryCount: 0,
signal: new AbortController().signal,
},
]);
expect(sends).toHaveLength(1);
expect(sends[0]!.queue).toBe(INTEGRATION_BACKFILL_ADMISSION_QUEUE);
expect(sends[0]!.data).toMatchObject({ kind: "google-health-backfill" });
});
it("wires the self-converging Google Health backfill boot discovery", () => {
expect(source).toMatch(
/enqueueBootTimeGoogleHealthBackfill\(\s*bootStaggerSecondsFor\("google-health-backfill"\)/,
);
expect(discoveryOf("integration_sync")).toContain("google-health-backfill");
});
it("imports the Google Health sync driver + backfill exports", () => {
expect(source).toMatch(/from\s*["']@\/lib\/google-health\/sync["']/);
expect(source).toMatch(
/from\s*["']@\/lib\/jobs\/google-health-backfill["']/,
);
it("imports the Google Health sync driver into the handler module", () => {
expect(handlerSource).toMatch(/from\s*["']@\/lib\/google-health\/sync["']/);
});
});
@@ -1,5 +1,5 @@
/**
* v1.7.0 — unit tests for the nightly insight pre-generation cron.
* Unit tests for the nightly insight pre-generation cron.
*
* Covers:
* - the discovery query selects stale-cache users who kept AI analysis
@@ -14,12 +14,10 @@
* - the warm pass is single-locale + refill-only (v1.16.8);
* - the forced single-user warm is idempotent (freshness re-check),
* backs off after a failure, and is capped per day (v1.16.8);
* - the queue is registered in `allQueues` AND scheduled in
* reminder-worker.ts (the v1.4.37 W10 unregistered-queue catch).
* - the worker registry declares the queue, schedules its cron and binds
* its handler (the unregistered-queue catch).
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import fs from "node:fs";
import path from "node:path";
const checkRateLimit = vi.fn();
const getAssistantFlags = vi.fn();
@@ -128,8 +126,18 @@ import {
PREGENERATE_STALE_MS,
FORCE_WARM_DAILY_LIMIT,
INSIGHT_PREGENERATE_QUEUE,
INSIGHT_PREGENERATE_CRON,
} from "../insight-pregenerate";
import {
INSIGHT_PREGENERATE_CRON,
INSIGHT_PREGENERATE_EXPIRE_SECONDS,
} from "../insight-pregenerate-shared";
import { handleInsightPregenerateJob } from "../reminder/insights-handlers";
import { insightRetryOptions } from "../reminder/registrar-shared";
import {
groupOf,
jobOf,
loadedHandler,
} from "../registry/__tests__/registry-helpers";
import { AI_BUDGETS } from "@/lib/ai/ai-budgets";
function makePrisma(
@@ -1402,29 +1410,22 @@ describe("forceWarmUser — on-demand single-user warm (v1.8.7.1)", () => {
});
describe("queue registration", () => {
// v1.18.1 — the insight-pregenerate wiring moved out of the 2143-LOC
// reminder-worker boot file into the status registrar. The dead-queue guard
// follows the wiring there.
const workerSrc = fs.readFileSync(
path.resolve(__dirname, "../reminder/register-status.ts"),
"utf8",
);
it("registers the queue name in the allQueues createQueue loop", () => {
const match = workerSrc.match(/const allQueues\s*=\s*\[([\s\S]*?)\];/);
expect(match).not.toBeNull();
expect(match![1]).toMatch(/\bINSIGHT_PREGENERATE_QUEUE\b/);
it("declares the queue in the status group", () => {
expect(groupOf(INSIGHT_PREGENERATE_QUEUE).name).toBe("status");
});
it("schedules the cron in the schedules table (with retry policy)", () => {
expect(workerSrc).toMatch(
/\[\s*INSIGHT_PREGENERATE_QUEUE\s*,\s*INSIGHT_PREGENERATE_CRON\s*,\s*\{\s*\.\.\.insightRetryOptions\s*,\s*expireInSeconds:\s*INSIGHT_PREGENERATE_EXPIRE_SECONDS\s*,?\s*\}\s*,?\s*\]/,
);
it("schedules the cron with the retry policy", () => {
const job = jobOf(INSIGHT_PREGENERATE_QUEUE);
expect(job.cron).toBe(INSIGHT_PREGENERATE_CRON);
expect(job.scheduleOptions).toEqual({
...insightRetryOptions,
expireInSeconds: INSIGHT_PREGENERATE_EXPIRE_SECONDS,
});
});
it("registers a createAndWork handler for the queue", () => {
expect(workerSrc).toMatch(
/createAndWork[\s\S]{0,80}INSIGHT_PREGENERATE_QUEUE/,
it("binds the pre-generation handler to the queue", async () => {
expect(await loadedHandler(INSIGHT_PREGENERATE_QUEUE)).toBe(
handleInsightPregenerateJob,
);
});
@@ -1,18 +1,16 @@
/**
* v1.8.3 — unit tests for the on-demand per-metric status generation queue.
* Unit tests for the on-demand per-metric status generation queue.
*
* Covers:
* - the dispatch runs the matching generator with force:true;
* - an unknown metric is annotated and skipped, not thrown;
* - the enqueue helper no-ops without a global boss instance and de-dupes
* with a singletonKey when one is present;
* - the queue is registered in `allQueues` AND wired to a `createAndWork`
* handler in reminder-worker.ts (the v1.4.37 W10 unregistered-queue
* catch — a queue built but never registered silently never drains).
* - the worker registry declares the queue and binds its handler (the
* unregistered-queue catch — a queue built but never registered silently
* never drains).
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import fs from "node:fs";
import path from "node:path";
const getGlobalBoss = vi.fn();
vi.mock("@/lib/jobs/boss-instance", () => ({
@@ -47,6 +45,12 @@ vi.mock("@/lib/ai/capabilities/gate", () => ({
aiCapabilityForJob: (...a: unknown[]) => aiCapabilityForJob(...a),
aiCapabilityForRecord: vi.fn(),
}));
// The worker's insights handlers (loaded to check the queue's binding) also
// import the nightly status batch; stub it so its card table never reads the
// stubbed generators above.
vi.mock("@/lib/insights/status-batch", () => ({
generateStatusBatchForUser: vi.fn(),
}));
const annotate = vi.fn();
vi.mock("@/lib/logging/context", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/logging/context")>()),
@@ -59,6 +63,13 @@ import {
INSIGHT_STATUS_GENERATE_QUEUE,
INSIGHT_STATUS_METRICS,
} from "../insight-status-generate";
import { INSIGHT_STATUS_GENERATE_CONCURRENCY } from "../insight-status-generate-queue";
import { handleInsightStatusGenerate } from "../reminder/insights-handlers";
import {
groupOf,
jobOf,
loadedHandler,
} from "../registry/__tests__/registry-helpers";
beforeEach(() => {
vi.clearAllMocks();
@@ -259,23 +270,18 @@ describe("enqueueStatusGeneration", () => {
});
describe("queue registration", () => {
// v1.18.1 — the insight-status-generate wiring moved out of the 2143-LOC
// reminder-worker boot file into the status registrar. The dead-queue guard
// follows the wiring there.
const workerSrc = fs.readFileSync(
path.resolve(__dirname, "../reminder/register-status.ts"),
"utf8",
);
it("registers the queue name in the allQueues createQueue loop", () => {
const match = workerSrc.match(/const allQueues\s*=\s*\[([\s\S]*?)\];/);
expect(match).not.toBeNull();
expect(match![1]).toMatch(/\bINSIGHT_STATUS_GENERATE_QUEUE\b/);
it("declares the queue in the status group, low-concurrency and cron-free", () => {
expect(groupOf(INSIGHT_STATUS_GENERATE_QUEUE).name).toBe("status");
const job = jobOf(INSIGHT_STATUS_GENERATE_QUEUE);
expect(job.work).toEqual({
localConcurrency: INSIGHT_STATUS_GENERATE_CONCURRENCY,
});
expect(job.cron).toBeUndefined();
});
it("registers a createAndWork handler for the queue", () => {
expect(workerSrc).toMatch(
/createAndWork[\s\S]{0,120}INSIGHT_STATUS_GENERATE_QUEUE/,
it("binds the status handler to the queue", async () => {
expect(await loadedHandler(INSIGHT_STATUS_GENERATE_QUEUE)).toBe(
handleInsightStatusGenerate,
);
});
@@ -27,12 +27,14 @@ import type { PrismaClient } from "@/generated/prisma/client";
import { caches, __resetAllCachesForTests } from "@/lib/cache/server-cache";
import {
INTAKE_AUTO_SKIP_CRON,
INTAKE_AUTO_SKIP_GRACE_HOURS,
INTAKE_AUTO_SKIP_QUEUE,
medicationAutoMissDelayMs,
runIntakeAutoSkipPass,
} from "../intake-auto-skip";
import {
INTAKE_AUTO_SKIP_CRON,
INTAKE_AUTO_SKIP_QUEUE,
} from "../intake-auto-skip-queue";
interface FakeIntakeRow {
id: string;
@@ -39,15 +39,17 @@ import { consumeForIntake } from "@/lib/medications/inventory/consumption";
import { refreshDoseSlot } from "@/lib/medications/dose-day-refresh";
import { queueMedicationIntakeSync } from "@/lib/notifications/medication-intake-sync";
import {
INTAKE_AUTO_TAKE_CRON,
INTAKE_AUTO_TAKE_LOOKBACK_MS,
INTAKE_AUTO_TAKE_QUEUE,
autoTakeFloor,
decideSlot,
dueAutoTakeSlots,
isInPauseEra,
runIntakeAutoTakePass,
} from "../intake-auto-take";
import {
INTAKE_AUTO_TAKE_CRON,
INTAKE_AUTO_TAKE_QUEUE,
} from "../intake-auto-take-queue";
import { autoTakeOwnedFrom } from "../intake-auto-skip";
import { autoTakeRollingAnchor } from "@/lib/medications/auto-take";
import { computeDisplayDue } from "@/lib/medications/scheduling/next-due";
@@ -1,95 +1,102 @@
/**
* v1.8.2 — duplicate dose-slot dedup queue registration guard.
* Duplicate dose-slot dedup queue registration guard.
*
* Same source-text-grep approach as the drain + step-consolidation +
* mean-consolidation regression guards: assert the queue is registered
* in `allQueues`, a `createAndWork` handler is wired against it, and the
* boot-discovery enqueue helper is called — without booting pg-boss +
* Prisma. An unregistered queue silently never drains (the past incident
* this guard exists to prevent).
* Reads the worker registry: the queue is declared in the maintenance group
* with its daily cron, bound to the dedup handler, and offered again by the
* maintenance boot discovery. The bound handler runs against a mocked dedup
* module, so pg-boss and Prisma never boot. An unregistered queue silently
* never drains (the past incident this guard exists to prevent).
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
// v1.18.1 — the intake-slot-dedup wiring + boot discovery moved out of the
// 2143-LOC reminder-worker boot file into the maintenance registrar. The
// dead-queue guard follows the wiring there.
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-maintenance.ts",
const { dedupeUserIntakeSlots, enqueueBootTimeIntakeSlotDedup } = vi.hoisted(
() => ({
dedupeUserIntakeSlots: vi.fn(async () => ({
slotsCollapsed: 0,
rowsSoftDeleted: 0,
rowsNormalised: 0,
daysRecomputed: 0,
})),
enqueueBootTimeIntakeSlotDedup: vi.fn(async () => ({
enqueued: 0,
skipped: 0,
error: null,
})),
}),
);
const source = readFileSync(REGISTRAR_PATH, "utf8");
vi.mock("@/lib/medications/intake-slot-dedup", async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/medications/intake-slot-dedup")
>()),
dedupeUserIntakeSlots,
enqueueBootTimeIntakeSlotDedup,
}));
describe("reminder-worker — intake-slot-dedup wiring", () => {
it("imports the queue symbols from the intake-slot-dedup module", () => {
expect(source).toMatch(
/from\s*["']@\/lib\/medications\/intake-slot-dedup["']/,
import type { Job } from "pg-boss";
import { handleIntakeSlotDedup } from "@/lib/jobs/reminder/maintenance-handlers";
import {
discoveryOf,
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import {
INTAKE_SLOT_DEDUP_CONCURRENCY,
INTAKE_SLOT_DEDUP_QUEUE,
} from "@/lib/medications/intake-slot-dedup";
function job(data: { userId?: string }): Job<{ userId?: string }> {
return {
id: "j1",
name: INTAKE_SLOT_DEDUP_QUEUE,
data,
expireInSeconds: 60,
heartbeatSeconds: null,
retryCount: 0,
signal: new AbortController().signal,
};
}
beforeEach(() => {
vi.clearAllMocks();
});
describe("intake-slot-dedup registration", () => {
it("declares the intake-slot-dedup queue in the maintenance group", () => {
expect(groupOf(INTAKE_SLOT_DEDUP_QUEUE).name).toBe("maintenance");
const definition = jobOf(INTAKE_SLOT_DEDUP_QUEUE);
expect(definition.policy?.policy).toBe("exclusive");
expect(definition.work).toEqual({
localConcurrency: INTAKE_SLOT_DEDUP_CONCURRENCY,
});
});
it("binds the dedup handler, which runs the per-user dedup", async () => {
expect(await loadedHandler(INTAKE_SLOT_DEDUP_QUEUE)).toBe(
handleIntakeSlotDedup,
);
expect(source).toMatch(/\bINTAKE_SLOT_DEDUP_QUEUE\b/);
expect(source).toMatch(/\bdedupeUserIntakeSlots\b/);
expect(source).toMatch(/\benqueueBootTimeIntakeSlotDedup\b/);
await handleIntakeSlotDedup([job({ userId: "u1" })]);
expect(dedupeUserIntakeSlots).toHaveBeenCalledWith("u1");
expect(enqueueBootTimeIntakeSlotDedup).not.toHaveBeenCalled();
});
it("registers the intake-slot-dedup queue in the allQueues loop", () => {
const allQueuesMatch = source.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bINTAKE_SLOT_DEDUP_QUEUE\b/);
it("fires the boot-discovery pass", () => {
expect(discoveryOf("maintenance")).toContain("intake-slot-dedup");
});
it("registers a createAndWork handler against the intake-slot-dedup queue", () => {
// v1.15.19 — span widened from {0,400}: the handler gained the daily
// cron-tick dispatch branch (no `userId` → discovery fan-out) between
// the queue name and the per-user dedup call.
expect(source).toMatch(
/createAndWork[\s\S]{0,200}INTAKE_SLOT_DEDUP_QUEUE[\s\S]{0,1400}dedupeUserIntakeSlots/,
);
});
it("fires the boot-discovery enqueue helper", () => {
expect(source).toMatch(/await enqueueBootTimeIntakeSlotDedup\(\)/);
});
// v1.15.19 — the boot-only pass left duplicates created between deploys
// standing until the next worker restart. The queue now also carries a
// daily cron tick whose empty payload (no `userId`) dispatches the same
// discovery fan-out.
// The boot-only pass left duplicates created between deploys standing
// until the next worker restart. The queue also carries a daily cron tick
// whose empty payload (no `userId`) dispatches the same discovery fan-out.
it("declares a daily cron inside the 03:xx maintenance window", () => {
const cronMatch = source.match(
/const INTAKE_SLOT_DEDUP_CRON\s*=\s*["']([^"']+)["']/,
);
expect(cronMatch).not.toBeNull();
// Five-field cron, daily (day-of-month/month/day-of-week all `*`),
// inside the 03:xx maintenance window.
expect(cronMatch![1]).toMatch(/^\d{1,2} 3 \* \* \*$/);
expect(jobOf(INTAKE_SLOT_DEDUP_QUEUE).cron).toMatch(/^\d{1,2} 3 \* \* \*$/);
});
it("registers the cron in the schedules table", () => {
// v1.15.20 — the schedules tuple grew an optional send-options slot
// (retry policy for the insight queues); match on the assignment, not
// the exact tuple type.
// v1.18.1 — `schedules` is now a module-level const in the registrar, so it
// closes at column 0 (`\n];`) rather than the monolith's function-body
// two-space indent.
const schedulesMatch = source.match(
/const schedules:[^=]*=\s*\[([\s\S]*?)\n\];/,
);
expect(schedulesMatch).not.toBeNull();
expect(schedulesMatch![1]).toMatch(
/\[INTAKE_SLOT_DEDUP_QUEUE,\s*INTAKE_SLOT_DEDUP_CRON\]/,
);
});
it("dispatches a payload without userId to the discovery fan-out", () => {
// The handler must branch on the missing `userId` BEFORE the per-user
// dedup call, and the branch must run the discovery helper.
expect(source).toMatch(
/INTAKE_SLOT_DEDUP_QUEUE[\s\S]{0,800}if \(!userId\) \{[\s\S]{0,400}enqueueBootTimeIntakeSlotDedup\(\)/,
);
it("dispatches a payload without userId to the discovery fan-out", async () => {
await handleIntakeSlotDedup([job({})]);
expect(enqueueBootTimeIntakeSlotDedup).toHaveBeenCalledTimes(1);
expect(dedupeUserIntakeSlots).not.toHaveBeenCalled();
});
});
@@ -1,7 +1,74 @@
/**
* The shared full-history backfill admission: every provider backfill and
* repair forwards into one database-coordinated drain. The boot discovery,
* the drain's binding and the source queues' forwarding are read off the
* worker registry; the boot discovery runs against stubbed enqueue helpers
* that record the stagger each pass hands them.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import type { SendOptions } from "pg-boss";
const staggers = vi.hoisted(() => new Map<string, number>());
const recordStagger = vi.hoisted(() => (kind: string, stagger: number) => {
staggers.set(kind, stagger);
return { enqueued: 0, skipped: 0, error: null };
});
vi.mock("@/lib/jobs/whoop-backfill", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/jobs/whoop-backfill")>()),
enqueueBootTimeWhoopBackfill: async (stagger: number) =>
recordStagger("whoop-backfill", stagger),
}));
vi.mock("@/lib/jobs/fitbit-backfill", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/jobs/fitbit-backfill")>()),
enqueueBootTimeFitbitBackfill: async (stagger: number) =>
recordStagger("fitbit-backfill", stagger),
}));
vi.mock("@/lib/jobs/google-health-backfill", async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/jobs/google-health-backfill")
>()),
enqueueBootTimeGoogleHealthBackfill: async (stagger: number) =>
recordStagger("google-health-backfill", stagger),
}));
vi.mock("@/lib/jobs/google-health-sleep-repair", async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/jobs/google-health-sleep-repair")
>()),
enqueueBootTimeGoogleHealthSleepRepair: async (stagger: number) =>
recordStagger("google-health-sleep-repair", stagger),
}));
vi.mock("@/lib/jobs/sleep-timeline-backfill", async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/jobs/sleep-timeline-backfill")
>()),
enqueueBootTimeSleepTimelineBackfill: async (stagger: number) =>
recordStagger("sleep-timeline-backfill", stagger),
}));
vi.mock("@/lib/jobs/lab-biomarker-backfill", async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/jobs/lab-biomarker-backfill")
>()),
enqueueBootTimeLabBiomarkerBackfill: async (stagger: number) =>
recordStagger("lab-biomarker-backfill", stagger),
}));
vi.mock("@/lib/jobs/strava-backfill", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/jobs/strava-backfill")>()),
enqueueBootTimeStravaBackfill: async (stagger: number) =>
recordStagger("strava-backfill", stagger),
}));
vi.mock("@/lib/jobs/fitbit-sleep-repair", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/jobs/fitbit-sleep-repair")>()),
enqueueBootTimeFitbitSleepRepair: async (stagger: number) =>
recordStagger("fitbit-sleep-repair", stagger),
}));
vi.mock("@/lib/jobs/oura-backfill", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/jobs/oura-backfill")>()),
enqueueBootTimeOuraBackfill: async (stagger: number) =>
recordStagger("oura-backfill", stagger),
}));
import {
INTEGRATION_BACKFILL_ADMISSION_GROUP,
INTEGRATION_BACKFILL_ADMISSION_QUEUE,
@@ -12,12 +79,29 @@ import {
bootStaggerSecondsFor,
enqueueIntegrationBackfillAdmission,
integrationBackfillSourceOptions,
type IntegrationBackfillKind,
} from "../integration-backfill-admission";
const registrarSource = readFileSync(
join(__dirname, "../reminder/register-integration-sync.ts"),
"utf8",
);
import {
FITBIT_BACKFILL_QUEUE,
FITBIT_SLEEP_REPAIR_QUEUE,
FITBIT_SYNC_QUEUE,
} from "@/lib/fitbit/jobs/queues";
import { GOOGLE_HEALTH_BACKFILL_QUEUE } from "../google-health-backfill-queue";
import { GOOGLE_HEALTH_SLEEP_REPAIR_QUEUE } from "../google-health-sleep-repair-queue";
import {
GOOGLE_HEALTH_SYNC_QUEUE,
STRAVA_SYNC_QUEUE,
} from "../integration-poll-queues";
import { LAB_BIOMARKER_BACKFILL_QUEUE } from "../lab-biomarker-backfill";
import { OURA_BACKFILL_QUEUE } from "../oura-backfill-queue";
import {
groupNamed,
jobOf,
recordingBoss,
} from "../registry/__tests__/registry-helpers";
import { SLEEP_TIMELINE_BACKFILL_QUEUE } from "../sleep-timeline-backfill-queue";
import { STRAVA_BACKFILL_QUEUE } from "../strava-backfill-queue";
import { WHOOP_BACKFILL_QUEUE } from "../whoop-backfill-queue";
const providerSources = [
"whoop-backfill.ts",
@@ -32,7 +116,7 @@ const providerSources = [
].map((file) => readFileSync(join(__dirname, "..", file), "utf8"));
describe("shared full-history backfill admission", () => {
it("assigns every integration backfill a deterministic durable boot stagger", () => {
it("assigns every integration backfill a deterministic durable boot stagger", async () => {
expect(INTEGRATION_BACKFILL_BOOT_ORDER).toEqual([
"whoop-backfill",
"fitbit-backfill",
@@ -54,23 +138,14 @@ describe("shared full-history backfill admission", () => {
expect(source).toMatch(/startAfterSeconds:\s*number\s*=\s*0/);
}
const discoveryCalls = {
"whoop-backfill": "enqueueBootTimeWhoopBackfill",
"fitbit-backfill": "enqueueBootTimeFitbitBackfill",
"google-health-backfill": "enqueueBootTimeGoogleHealthBackfill",
"google-health-sleep-repair": "enqueueBootTimeGoogleHealthSleepRepair",
"sleep-timeline-backfill": "enqueueBootTimeSleepTimelineBackfill",
"lab-biomarker-backfill": "enqueueBootTimeLabBiomarkerBackfill",
"strava-backfill": "enqueueBootTimeStravaBackfill",
"fitbit-sleep-repair": "enqueueBootTimeFitbitSleepRepair",
"oura-backfill": "enqueueBootTimeOuraBackfill",
} as const;
// Every kind has a boot discovery pass, and each pass hands its helper
// the kind's own stagger.
const passes = groupNamed("integration_sync").discovery ?? [];
for (const kind of INTEGRATION_BACKFILL_BOOT_ORDER) {
expect(registrarSource).toMatch(
new RegExp(
`${discoveryCalls[kind]}\\(\\s*bootStaggerSecondsFor\\("${kind}"\\)`,
),
);
const pass = passes.find((p) => p.name === kind);
expect(pass, kind).toBeDefined();
await pass!.run();
expect(staggers.get(kind), kind).toBe(bootStaggerSecondsFor(kind));
}
});
@@ -139,40 +214,51 @@ describe("shared full-history backfill admission", () => {
).not.toHaveProperty("startAfter");
});
it("registers one shared globally bounded drain and leaves incremental workers outside it", () => {
expect(registrarSource).toMatch(
/INTEGRATION_BACKFILL_ADMISSION_QUEUE[\s\S]{0,220}localConcurrency:\s*INTEGRATION_BACKFILL_GLOBAL_CONCURRENCY[\s\S]{0,120}groupConcurrency:\s*INTEGRATION_BACKFILL_GLOBAL_CONCURRENCY/,
);
expect(registrarSource).toMatch(
/\[INTEGRATION_BACKFILL_ADMISSION_QUEUE\]:\s*\{[\s\S]{0,100}policy:\s*"exclusive"/,
);
it("registers one shared globally bounded drain and leaves incremental workers outside it", async () => {
const drain = jobOf(INTEGRATION_BACKFILL_ADMISSION_QUEUE);
expect(drain.work).toEqual({
localConcurrency: INTEGRATION_BACKFILL_GLOBAL_CONCURRENCY,
groupConcurrency: INTEGRATION_BACKFILL_GLOBAL_CONCURRENCY,
});
expect(drain.policy?.policy).toBe("exclusive");
for (const queue of [
"WHOOP_BACKFILL_QUEUE",
"FITBIT_BACKFILL_QUEUE",
"GOOGLE_HEALTH_BACKFILL_QUEUE",
"GOOGLE_HEALTH_SLEEP_REPAIR_QUEUE",
"SLEEP_TIMELINE_BACKFILL_QUEUE",
"LAB_BIOMARKER_BACKFILL_QUEUE",
"STRAVA_BACKFILL_QUEUE",
"FITBIT_SLEEP_REPAIR_QUEUE",
"OURA_BACKFILL_QUEUE",
]) {
expect(registrarSource).toMatch(
new RegExp(
`${queue}[\\s\\S]{0,500}enqueueIntegrationBackfillAdmission`,
),
);
const sources: ReadonlyArray<[string, IntegrationBackfillKind]> = [
[WHOOP_BACKFILL_QUEUE, "whoop-backfill"],
[FITBIT_BACKFILL_QUEUE, "fitbit-backfill"],
[GOOGLE_HEALTH_BACKFILL_QUEUE, "google-health-backfill"],
[GOOGLE_HEALTH_SLEEP_REPAIR_QUEUE, "google-health-sleep-repair"],
[SLEEP_TIMELINE_BACKFILL_QUEUE, "sleep-timeline-backfill"],
[LAB_BIOMARKER_BACKFILL_QUEUE, "lab-biomarker-backfill"],
[STRAVA_BACKFILL_QUEUE, "strava-backfill"],
[FITBIT_SLEEP_REPAIR_QUEUE, "fitbit-sleep-repair"],
[OURA_BACKFILL_QUEUE, "oura-backfill"],
];
for (const [queue, kind] of sources) {
const { boss, sends } = recordingBoss();
const handler = await jobOf(queue).load({ boss });
await handler([
{
id: "j1",
name: queue,
data: { userId: "u1", provider: "WHOOP" },
expireInSeconds: 60,
heartbeatSeconds: null,
retryCount: 0,
signal: new AbortController().signal,
},
]);
expect(sends, queue).toHaveLength(1);
expect(sends[0]!.queue).toBe(INTEGRATION_BACKFILL_ADMISSION_QUEUE);
expect(sends[0]!.data).toMatchObject({ kind });
}
for (const queue of [
"FITBIT_SYNC_QUEUE",
"GOOGLE_HEALTH_SYNC_QUEUE",
"STRAVA_SYNC_QUEUE",
FITBIT_SYNC_QUEUE,
GOOGLE_HEALTH_SYNC_QUEUE,
STRAVA_SYNC_QUEUE,
]) {
expect(registrarSource).toMatch(
new RegExp(`${queue}[\\s\\S]{0,100}localConcurrency:\\s*1`),
);
expect(jobOf(queue).work, queue).toEqual({ localConcurrency: 1 });
expect(jobOf(queue).lane, queue).toBeUndefined();
}
});
});
@@ -1,57 +1,49 @@
/**
* MCP Phase 3 (M2) — mcp-token-cleanup queue registration guard.
* Mcp-token-cleanup queue registration guard.
*
* Source-text-grep guard like the other queue-wiring checks: assert the queue
* is in `allQueues`, scheduled, and wired to a `createAndWork` handler. An
* unregistered queue silently never drains (the recurring v1.4.37 dead-queue
* bug), which would leave expired MCP connector access tokens growing unbounded.
* Reads the worker registry: the queue is declared in the maintenance group,
* scheduled with the cron as its retry, and bound to the cleanup handler. An
* unregistered queue silently never drains (the dead-queue class), which
* would leave expired MCP connector access tokens growing unbounded.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-maintenance.ts",
import { handleMcpTokenCleanup } from "@/lib/jobs/reminder/cleanup-handlers";
import { cronIsTheRetry } from "@/lib/jobs/reminder/registrar-shared";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const QUEUE = "mcp-token-cleanup";
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "cleanup-handlers.ts"),
"utf8",
);
const workerSource =
readFileSync(REGISTRAR_PATH, "utf8") +
readFileSync(
join(__dirname, "..", "reminder", "cleanup-handlers.ts"),
"utf8",
);
describe("reminder-worker — mcp-token-cleanup wiring", () => {
it("imports the cleanup handler from the cleanup-handlers module", () => {
expect(workerSource).toMatch(/\bhandleMcpTokenCleanup\b/);
expect(workerSource).toMatch(/\bMcpTokenCleanupPayload\b/);
describe("mcp-token-cleanup registration", () => {
it("declares the queue in the maintenance group", () => {
expect(groupOf(QUEUE).name).toBe("maintenance");
});
it("registers the mcp-token-cleanup queue in the allQueues loop", () => {
const allQueuesMatch = workerSource.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bMCP_TOKEN_CLEANUP_QUEUE\b/);
it("schedules the daily cron with the cron as the retry", () => {
const job = jobOf(QUEUE);
expect(job.cron).toBe("0 4 * * *");
expect(job.scheduleOptions).toBe(cronIsTheRetry);
expect(job.work).toEqual({ localConcurrency: 1 });
});
it("schedules the mcp-token-cleanup cron", () => {
expect(workerSource).toMatch(
/\[MCP_TOKEN_CLEANUP_QUEUE,\s*MCP_TOKEN_CLEANUP_CRON,\s*cronIsTheRetry\]/,
);
});
it("registers a createAndWork handler for the queue", () => {
expect(workerSource).toMatch(
/createAndWork[\s\S]{0,200}MCP_TOKEN_CLEANUP_QUEUE[\s\S]{0,200}handleMcpTokenCleanup/,
);
it("binds the cleanup handler", async () => {
expect(await loadedHandler(QUEUE)).toBe(handleMcpTokenCleanup);
});
it("prunes MCP tokens inside the handler", () => {
expect(workerSource).toMatch(
expect(handlerSource).toMatch(
/handleMcpTokenCleanup[\s\S]{0,400}cleanupExpiredMcpTokens/,
);
});
@@ -1,48 +1,44 @@
/**
* v1.7.0 — daily-mean consolidation queue registration guard.
* Daily-mean consolidation queue registration guard.
*
* Same source-text-grep approach as the drain + step-consolidation
* regression guards: assert the queue is registered in `allQueues`, a
* `createAndWork` handler is wired against it, and the boot-discovery
* enqueue helper is called — without booting pg-boss + Prisma. An
* unregistered queue silently never drains.
* Reads the worker registry: the queue is declared in the rollup group,
* bound to the consolidation handler, and offered again by the rollup boot
* discovery. An unregistered queue silently never drains.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the mean-consolidation wiring + boot discovery moved out of the
// 2143-LOC reminder-worker boot file into the rollup registrar. The dead-queue
// guard follows the wiring there.
const REGISTRAR_PATH = join(__dirname, "..", "reminder", "register-rollup.ts");
const source = readFileSync(REGISTRAR_PATH, "utf8");
import {
MEAN_CONSOLIDATION_CONCURRENCY,
MEAN_CONSOLIDATION_QUEUE,
} from "@/lib/jobs/mean-consolidation";
import { handleMeanConsolidation } from "@/lib/jobs/reminder/rollup-handlers";
import {
discoveryOf,
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
describe("reminder-worker — mean-consolidation wiring", () => {
it("imports the queue symbols from the mean-consolidation module", () => {
expect(source).toMatch(/from\s*["']@\/lib\/jobs\/mean-consolidation["']/);
expect(source).toMatch(/\bMEAN_CONSOLIDATION_QUEUE\b/);
expect(source).toMatch(/\brunMeanConsolidationForUser\b/);
expect(source).toMatch(/\benqueueBootTimeMeanConsolidation\b/);
describe("mean-consolidation registration", () => {
it("declares the mean-consolidation queue in the rollup group", () => {
expect(groupOf(MEAN_CONSOLIDATION_QUEUE).name).toBe("rollup");
});
it("registers the mean-consolidation queue in the allQueues loop", () => {
const allQueuesMatch = source.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bMEAN_CONSOLIDATION_QUEUE\b/);
it("is a per-user exclusive pass at the module's concurrency", () => {
const job = jobOf(MEAN_CONSOLIDATION_QUEUE);
expect(job.policy?.policy).toBe("exclusive");
expect(job.work).toEqual({
localConcurrency: MEAN_CONSOLIDATION_CONCURRENCY,
});
});
it("registers a createAndWork handler against the mean-consolidation queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}MEAN_CONSOLIDATION_QUEUE[\s\S]{0,400}runMeanConsolidationForUser/,
it("binds the consolidation handler", async () => {
expect(await loadedHandler(MEAN_CONSOLIDATION_QUEUE)).toBe(
handleMeanConsolidation,
);
});
it("fires the boot-discovery enqueue helper", () => {
// The boot wiring passes a stagger offset so the heavy backfills don't all
// drain in parallel onto one tenant; allow an optional argument here.
expect(source).toMatch(/await enqueueBootTimeMeanConsolidation\([^)]*\)/);
it("runs the boot discovery pass", () => {
expect(discoveryOf("rollup")).toContain("mean-consolidation");
});
});
@@ -1,90 +1,81 @@
/**
* v1.17.1 — measurement-reminder queue registration guard.
* Measurement-reminder queue registration guard.
*
* Same source-text-grep approach as the cycle / mood / drain registration
* guards: assert the Vorsorge-reminder queue is declared, listed in
* `allQueues` (the v1.4.37 dead-queue lesson — an unregistered queue is
* never provisioned and the schedule silently no-ops), scheduled on its
* cron, and wired to a `createAndWork` handler that delegates to
* `runMeasurementReminderTick`. No pg-boss / Prisma boot.
* Reads the worker registry: the Vorsorge-reminder queue is declared in the
* reminders group (an unregistered queue is never provisioned and the
* schedule silently no-ops), scheduled on its cron, and bound to the handler
* that delegates to `runMeasurementReminderTick`; the eventful
* reminder-satisfy queue sits beside it. No pg-boss / Prisma boot.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the reminder-dispatch wiring (measurement reminder +
// reminder-satisfy) moved out of the 2143-LOC reminder-worker boot file into
// the reminders registrar. The dead-queue guard follows the wiring there (the
// handler module is still concatenated for the handler-symbol assertions).
const source =
readFileSync(
join(__dirname, "..", "reminder", "register-reminders.ts"),
"utf8",
) +
readFileSync(
join(__dirname, "..", "reminder", "mood-cycle-checks.ts"),
"utf8",
);
import {
REMINDER_SATISFY_CONCURRENCY,
REMINDER_SATISFY_QUEUE,
} from "@/lib/jobs/reminder-satisfy";
import {
handleMeasurementReminderCheck,
handleReminderSatisfy,
} from "@/lib/jobs/reminder/mood-cycle-checks";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
describe("reminder-worker — measurement-reminder wiring", () => {
const MEASUREMENT_REMINDER_QUEUE = "measurement-reminder-check";
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "mood-cycle-checks.ts"),
"utf8",
);
describe("measurement-reminder registration", () => {
it("imports the measurement-reminder tick runner", () => {
expect(source).toMatch(/from\s*["']@\/lib\/jobs\/measurement-reminder["']/);
expect(source).toMatch(/\brunMeasurementReminderTick\b/);
expect(handlerSource).toMatch(
/from\s*["']@\/lib\/jobs\/measurement-reminder["']/,
);
expect(handlerSource).toMatch(/\brunMeasurementReminderTick\b/);
});
it("declares the queue + cron constants", () => {
expect(source).toMatch(
/MEASUREMENT_REMINDER_QUEUE\s*=\s*["']measurement-reminder-check["']/,
);
expect(source).toMatch(
/MEASUREMENT_REMINDER_CRON\s*=\s*["']\*\/15 \* \* \* \*["']/,
);
it("declares the queue in the reminders group", () => {
expect(groupOf(MEASUREMENT_REMINDER_QUEUE).name).toBe("reminders");
});
it("registers the queue in the allQueues loop", () => {
const allQueuesMatch = source.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bMEASUREMENT_REMINDER_QUEUE\b/);
it("schedules the measurement-reminder cron, single-flight", () => {
const job = jobOf(MEASUREMENT_REMINDER_QUEUE);
expect(job.cron).toBe("*/15 * * * *");
expect(job.work).toEqual({ localConcurrency: 1 });
});
it("schedules the measurement-reminder cron", () => {
expect(source).toMatch(
/\[MEASUREMENT_REMINDER_QUEUE,\s*MEASUREMENT_REMINDER_CRON\]/,
it("binds the handler that runs the tick", async () => {
expect(await loadedHandler(MEASUREMENT_REMINDER_QUEUE)).toBe(
handleMeasurementReminderCheck,
);
});
it("registers a createAndWork handler that runs the tick", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}MEASUREMENT_REMINDER_QUEUE[\s\S]{0,200}handleMeasurementReminderCheck/,
);
expect(source).toMatch(
expect(handlerSource).toMatch(
/handleMeasurementReminderCheck[\s\S]{0,400}runMeasurementReminderTick/,
);
});
});
describe("reminder-worker — eventful reminder-satisfy wiring (v1.18.1)", () => {
it("imports the satisfy queue constants", () => {
expect(source).toMatch(/from\s*["']@\/lib\/jobs\/reminder-satisfy["']/);
expect(source).toMatch(/\bREMINDER_SATISFY_QUEUE\b/);
describe("eventful reminder-satisfy registration", () => {
it("declares the satisfy queue in the reminders group, with no cron", () => {
expect(groupOf(REMINDER_SATISFY_QUEUE).name).toBe("reminders");
const job = jobOf(REMINDER_SATISFY_QUEUE);
expect(job.cron).toBeUndefined();
expect(job.work).toEqual({
localConcurrency: REMINDER_SATISFY_CONCURRENCY,
});
});
it("registers the satisfy queue in the allQueues loop", () => {
const allQueuesMatch = source.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
it("binds the satisfy handler", async () => {
expect(await loadedHandler(REMINDER_SATISFY_QUEUE)).toBe(
handleReminderSatisfy,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bREMINDER_SATISFY_QUEUE\b/);
});
it("binds a createAndWork handler for the satisfy queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}REMINDER_SATISFY_QUEUE[\s\S]{0,200}handleReminderSatisfy/,
);
expect(source).toMatch(
expect(handlerSource).toMatch(
/handleReminderSatisfy[\s\S]{0,800}runReminderSatisfyForUser/,
);
});
@@ -1,23 +1,26 @@
/**
* v1.7.0 — measurement-tombstone retention cleanup.
* Measurement-tombstone retention cleanup.
*
* Two guards:
* 1. Behavioural: the helper hard-deletes only soft-deleted rows whose
* `deletedAt` predates the retention horizon, leaving live rows and
* recently-tombstoned rows untouched.
* 2. Source-grep wiring (same approach as the drain-cumulative guard):
* the queue is registered in `allQueues`, scheduled, and bound to a
* `createAndWork` handler — a missing `allQueues` entry silently no-ops
* the schedule under pg-boss v12.
* 2. Registry wiring: the queue is declared in the maintenance group,
* scheduled, and bound to its handler — a queue the registry does not
* declare is never created, and its schedule silently no-ops under
* pg-boss v12.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import { cleanupExpiredMeasurementTombstones } from "../measurement-tombstone-cleanup";
import { PURGE_BATCH_SIZE, PURGE_MAX_BATCHES } from "../purge-batch";
import { TOMBSTONE_RETENTION_DAYS } from "@/lib/auth/native-client";
import { handleMeasurementTombstoneCleanup } from "../reminder/cleanup-handlers";
import {
groupOf,
jobOf,
loadedHandler,
} from "../registry/__tests__/registry-helpers";
const DAY_MS = 86_400_000;
@@ -170,42 +173,24 @@ describe("cleanupExpiredMeasurementTombstones — accounts under restore", () =>
});
});
describe("reminder-worker — measurement-tombstone-cleanup schedule", () => {
// v1.18.1 — the cleanup wiring moved out of the 2143-LOC reminder-worker
// boot file into the maintenance registrar. The dead-queue guard follows it.
const source = readFileSync(
join(__dirname, "..", "reminder", "register-maintenance.ts"),
"utf8",
);
describe("measurement-tombstone-cleanup schedule", () => {
const QUEUE = "measurement-tombstone-cleanup";
it("declares the queue at the documented Berlin cadence", () => {
expect(source).toMatch(
/MEASUREMENT_TOMBSTONE_CLEANUP_QUEUE\s*=\s*["']measurement-tombstone-cleanup["']/,
);
expect(source).toMatch(
/MEASUREMENT_TOMBSTONE_CLEANUP_CRON\s*=\s*["']40 3 \* \* \*["']/,
);
expect(jobOf(QUEUE).cron).toBe("40 3 * * *");
});
it("registers the queue in the allQueues createQueue loop", () => {
const allQueuesMatch = source.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(
/\bMEASUREMENT_TOMBSTONE_CLEANUP_QUEUE\b/,
);
it("declares the queue in the maintenance group", () => {
expect(groupOf(QUEUE).name).toBe("maintenance");
});
it("schedules the cron via boss.schedule (allQueues + schedules)", () => {
expect(source).toMatch(
/\[MEASUREMENT_TOMBSTONE_CLEANUP_QUEUE,\s*MEASUREMENT_TOMBSTONE_CLEANUP_CRON\]/,
);
it("keeps the default retries on the schedule", () => {
const job = jobOf(QUEUE);
expect(job.scheduleOptions).toBeUndefined();
expect(job.work).toEqual({ localConcurrency: 1 });
});
it("binds a createAndWork handler to the queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}MEASUREMENT_TOMBSTONE_CLEANUP_QUEUE[\s\S]{0,200}handleMeasurementTombstoneCleanup/,
);
it("binds the cleanup handler to the queue", async () => {
expect(await loadedHandler(QUEUE)).toBe(handleMeasurementTombstoneCleanup);
});
});
@@ -46,11 +46,13 @@ const predictionFindManyMock = vi.fn();
const deleteManyMock = vi.fn();
import {
MOOD_PROGNOSIS_REFRESH_CRON,
MOOD_PROGNOSIS_REFRESH_QUEUE,
handleMoodPrognosisRefresh,
runMoodPrognosisRefresh,
} from "@/lib/jobs/mood-prognosis-refresh";
import {
MOOD_PROGNOSIS_REFRESH_CRON,
MOOD_PROGNOSIS_REFRESH_QUEUE,
} from "@/lib/jobs/mood-prognosis-refresh-queue";
import { MIN_ENTRIES_FOR_FORECAST } from "@/lib/analytics/mood-prognosis/thresholds";
const NOW = new Date("2026-08-08T02:35:00.000Z");
@@ -1,96 +1,78 @@
/**
* v1.17.0 — pg-boss queue registration for the three new poll-only sync layers:
* Nightscout CGM, Polar, and Oura.
* pg-boss queue registration for the three poll-only sync layers: Nightscout
* CGM, Polar, and Oura.
*
* Source-as-text guard (the reminder-worker pulls heavy infra we don't boot
* here): assert each queue constant + cron + handler wiring + allQueues
* membership are present — the fast guard against the v1.4.37 dead-queue class
* (a queue declared but never registered in `allQueues`, which silently never
* drains). All three syncs are poll-only (no webhook, no backfill queue): a
* single hourly tick walks every configured account.
* Reads the worker registry (the worker pulls heavy infra we don't boot
* here): each queue is declared in the integration-sync group with its cron
* and handler — the fast guard against the dead-queue class (a queue declared
* but never registered, which silently never drains). All three syncs are
* poll-only (no webhook): a single hourly tick walks every configured
* account. The queue names live in `integration-poll-queues.ts`, so an OAuth
* callback can enqueue a connect-time first sync without the handler graph.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the queue wiring moved out of the 2143-LOC reminder-worker boot
// file into domain registrars; the Nightscout / Polar / Oura queues live in the
// integration-sync registrar. The dead-queue guard follows the wiring there
// (the handler module is still concatenated for the handler-symbol assertions).
//
// v1.32.28 — the four poll-queue NAMES moved again, into
// `integration-poll-queues.ts`, so an OAuth callback can enqueue a connect-time
// first sync without importing the registrar's handler graph. The guard follows
// them: the name declarations are asserted against that module, everything else
// (allQueues membership, cron schedule, handler binding) still against the
// registrar.
const source =
readFileSync(join(__dirname, "..", "integration-poll-queues.ts"), "utf8") +
readFileSync(
join(__dirname, "..", "reminder", "register-integration-sync.ts"),
"utf8",
) +
readFileSync(join(__dirname, "..", "reminder", "nightscout-sync.ts"), "utf8");
import {
NIGHTSCOUT_SYNC_CRON,
NIGHTSCOUT_SYNC_QUEUE,
OURA_SYNC_CRON,
OURA_SYNC_QUEUE,
POLAR_SYNC_CRON,
POLAR_SYNC_QUEUE,
} from "@/lib/jobs/integration-poll-queues";
import { handleNightscoutSync } from "@/lib/jobs/reminder/nightscout-sync";
import { handleOuraSync } from "@/lib/jobs/reminder/oura-sync";
import { handlePolarSync } from "@/lib/jobs/reminder/polar-sync";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
describe("reminder-worker — Nightscout sync queue", () => {
describe("Nightscout sync queue", () => {
it("declares the nightscout-sync queue constant", () => {
expect(source).toMatch(
/NIGHTSCOUT_SYNC_QUEUE\s*=\s*["']nightscout-sync["']/,
);
expect(NIGHTSCOUT_SYNC_QUEUE).toBe("nightscout-sync");
});
it("registers the queue in allQueues (v1.4.37 dead-queue guard)", () => {
const block = /const allQueues = \[([\s\S]*?)\];/.exec(source);
expect(block).not.toBeNull();
expect(block![1]!).toContain("NIGHTSCOUT_SYNC_QUEUE");
it("declares the queue in the integration-sync group", () => {
expect(groupOf(NIGHTSCOUT_SYNC_QUEUE).name).toBe("integration_sync");
});
it("schedules the hourly poll cron", () => {
expect(source).toMatch(/\[NIGHTSCOUT_SYNC_QUEUE,\s*NIGHTSCOUT_SYNC_CRON\]/);
expect(jobOf(NIGHTSCOUT_SYNC_QUEUE).cron).toBe(NIGHTSCOUT_SYNC_CRON);
});
it("registers a createAndWork handler for the queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,160}NIGHTSCOUT_SYNC_QUEUE[\s\S]{0,160}handleNightscoutSync/,
it("binds the handler for the queue", async () => {
expect(await loadedHandler(NIGHTSCOUT_SYNC_QUEUE)).toBe(
handleNightscoutSync,
);
});
});
describe("reminder-worker — Polar / Oura sync queues", () => {
// The same v1.4.37 dead-queue class applies to the two OAuth-wearable polls
// shipped alongside Nightscout in v1.17.0 — assert their allQueues membership
// so a future refactor can't silently drop one and starve the sync.
const allQueuesBlock = () => {
const block = /const allQueues = \[([\s\S]*?)\];/.exec(source);
expect(block).not.toBeNull();
return block![1]!;
};
describe("Polar / Oura sync queues", () => {
// The same dead-queue class applies to the two OAuth-wearable polls shipped
// alongside Nightscout — assert their registry membership so a future
// refactor can't silently drop one and starve the sync.
it("declares the polar-sync and oura-sync queue constants", () => {
expect(source).toMatch(/POLAR_SYNC_QUEUE\s*=\s*["']polar-sync["']/);
expect(source).toMatch(/OURA_SYNC_QUEUE\s*=\s*["']oura-sync["']/);
expect(POLAR_SYNC_QUEUE).toBe("polar-sync");
expect(OURA_SYNC_QUEUE).toBe("oura-sync");
});
it("registers POLAR_SYNC_QUEUE in allQueues (v1.4.37 dead-queue guard)", () => {
expect(allQueuesBlock()).toContain("POLAR_SYNC_QUEUE");
it("declares POLAR_SYNC_QUEUE in the integration-sync group", () => {
expect(groupOf(POLAR_SYNC_QUEUE).name).toBe("integration_sync");
});
it("registers OURA_SYNC_QUEUE in allQueues (v1.4.37 dead-queue guard)", () => {
expect(allQueuesBlock()).toContain("OURA_SYNC_QUEUE");
it("declares OURA_SYNC_QUEUE in the integration-sync group", () => {
expect(groupOf(OURA_SYNC_QUEUE).name).toBe("integration_sync");
});
it("schedules the hourly poll cron for both", () => {
expect(source).toMatch(/\[POLAR_SYNC_QUEUE,\s*POLAR_SYNC_CRON\]/);
expect(source).toMatch(/\[OURA_SYNC_QUEUE,\s*OURA_SYNC_CRON\]/);
expect(jobOf(POLAR_SYNC_QUEUE).cron).toBe(POLAR_SYNC_CRON);
expect(jobOf(OURA_SYNC_QUEUE).cron).toBe(OURA_SYNC_CRON);
});
it("registers a createAndWork handler for each queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,160}POLAR_SYNC_QUEUE[\s\S]{0,160}handlePolarSync/,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,160}OURA_SYNC_QUEUE[\s\S]{0,160}handleOuraSync/,
);
it("binds a handler for each queue", async () => {
expect(await loadedHandler(POLAR_SYNC_QUEUE)).toBe(handlePolarSync);
expect(await loadedHandler(OURA_SYNC_QUEUE)).toBe(handleOuraSync);
});
});
@@ -1,6 +1,4 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import fs from "node:fs";
import path from "node:path";
const checkRateLimit = vi.fn();
const getAssistantFlags = vi.fn();
@@ -34,6 +32,14 @@ import {
PERIOD_NARRATIVE_QUEUE,
PERIOD_NARRATIVE_CRON,
} from "../period-narrative-warm";
import { PERIOD_NARRATIVE_EXPIRE_SECONDS } from "../period-narrative-queue";
import { handlePeriodNarrative } from "../reminder/status-handlers";
import { insightRetryOptions } from "../reminder/registrar-shared";
import {
groupOf,
jobOf,
loadedHandler,
} from "../registry/__tests__/registry-helpers";
function makePrisma(
users: Array<{ id: string; locale: string | null }>,
@@ -234,29 +240,22 @@ describe("runPeriodNarrativeWarm", () => {
});
describe("queue registration", () => {
// v1.18.1 — the period-narrative wiring moved out of the 2143-LOC
// reminder-worker boot file into the status registrar. The dead-queue guard
// follows the wiring there.
const workerSrc = fs.readFileSync(
path.resolve(__dirname, "../reminder/register-status.ts"),
"utf8",
);
it("registers the queue in the allQueues createQueue loop", () => {
const match = workerSrc.match(/const allQueues\s*=\s*\[([\s\S]*?)\];/);
expect(match).not.toBeNull();
expect(match![1]).toMatch(/\bPERIOD_NARRATIVE_QUEUE\b/);
it("declares the queue in the status group", () => {
expect(groupOf(PERIOD_NARRATIVE_QUEUE).name).toBe("status");
});
it("schedules the cron in the schedules table (with retry policy)", () => {
expect(workerSrc).toMatch(
/\[\s*PERIOD_NARRATIVE_QUEUE\s*,\s*PERIOD_NARRATIVE_CRON\s*,\s*\{\s*\.\.\.insightRetryOptions\s*,\s*expireInSeconds:\s*PERIOD_NARRATIVE_EXPIRE_SECONDS\s*,?\s*\}\s*,?\s*\]/,
);
it("schedules the cron with the retry policy", () => {
const job = jobOf(PERIOD_NARRATIVE_QUEUE);
expect(job.cron).toBe(PERIOD_NARRATIVE_CRON);
expect(job.scheduleOptions).toEqual({
...insightRetryOptions,
expireInSeconds: PERIOD_NARRATIVE_EXPIRE_SECONDS,
});
});
it("registers a createAndWork handler for the queue", () => {
expect(workerSrc).toMatch(
/createAndWork[\s\S]{0,120}PERIOD_NARRATIVE_QUEUE/,
it("binds the warm handler to the queue", async () => {
expect(await loadedHandler(PERIOD_NARRATIVE_QUEUE)).toBe(
handlePeriodNarrative,
);
});
@@ -1,160 +1,138 @@
/**
* Dead-policy guard, in the same spirit as the v1.4.37 dead-queue guards.
* Dead-policy guard, in the same spirit as the dead-queue guards.
*
* A queue's `singletonKey` only de-duplicates if its registrar names a policy
* for it: pg-boss constrains `singleton_key` through partial unique indexes
* scoped by queue policy, and the default `standard` policy has none. So the
* enqueue site and the registrar entry are two halves of one guarantee that
* live in different files, and nothing in the type system couples them —
* deleting the registrar entry leaves the `singletonKey` at the call site
* looking entirely correct while it silently stops doing anything.
* A queue's `singletonKey` only de-duplicates if its registry declaration
* names a policy for it: pg-boss constrains `singleton_key` through partial
* unique indexes scoped by queue policy, and the default `standard` policy
* has none. So the enqueue site and the declaration are two halves of one
* guarantee that live in different files, and nothing in the type system
* couples them — dropping the declaration's policy leaves the `singletonKey`
* at the call site looking entirely correct while it silently stops doing
* anything.
*
* These assertions are that coupling. Each queue below is listed with the
* policy its call site depends on; dropping or weakening an entry turns this
* suite red instead of quietly restoring the storm.
* These assertions are that coupling. Each queue below is listed by its wire
* name with the policy its call site depends on, and must be declared in the
* named registry group; dropping or weakening an entry turns this suite red
* instead of quietly restoring the storm.
*
* The behavioural proof that the policies mean what the names say lives in
* `tests/integration/queue-singleton-policy.integration.test.ts`, which runs a
* real pg-boss against a real Postgres.
*/
import fs from "node:fs";
import path from "node:path";
import { describe, expect, it } from "vitest";
const read = (file: string) =>
fs.readFileSync(path.resolve(__dirname, "../reminder", file), "utf8");
import type { JobGroupName } from "@/lib/jobs/registry/define-job";
import {
groupNamed,
groupOf,
jobOf,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
/**
* Assert that `queue` appears in the registrar's `queuePolicies` table with
* `policy`. Anchored to the table so an unrelated mention of the constant
* elsewhere in the file cannot satisfy it.
*/
function expectPolicy(source: string, queue: string, policy: string) {
const table = source.match(
/const queuePolicies: QueuePolicyTable = \{([\s\S]*?)\n\};/,
);
expect(table, "registrar has no queuePolicies table").not.toBeNull();
const entry = new RegExp(`\\[${queue}\\]:\\s*\\{\\s*policy:\\s*"${policy}"`);
/** Assert that `queue` is declared in `group` with `policy`. */
function expectPolicy(group: JobGroupName, queue: string, policy: string) {
expect(groupOf(queue).name).toBe(group);
expect(
table![1],
jobOf(queue).policy?.policy,
`${queue} must carry the "${policy}" policy — without it the singletonKey at its enqueue site is inert`,
).toMatch(entry);
).toBe(policy);
}
describe("rollup registrar queue policies", () => {
const source = read("register-rollup.ts");
describe("rollup group queue policies", () => {
// `short`, not `exclusive`: the handler re-reads the bucket when it starts,
// so only QUEUED duplicates may be collapsed. Promoting either of these to
// `exclusive` would strand a measurement written while a recompute ran.
it.each([
["ROLLUP_RECOMPUTE_QUEUE", "short"],
["MOOD_ROLLUP_RECOMPUTE_QUEUE", "short"],
["rollup-recompute", "short"],
["mood-rollup-recompute", "short"],
])("%s is %s", (queue, policy) => {
expectPolicy(source, queue, policy);
expectPolicy("rollup", queue, policy);
});
// Per-user, self-converging backfills: a second concurrent run is pure
// duplicated work and discovery re-offers the job while it is outstanding.
it.each([
["ROLLUP_FULL_BACKFILL_QUEUE", "exclusive"],
["MOOD_ROLLUP_FULL_BACKFILL_QUEUE", "exclusive"],
["STEP_CONSOLIDATION_QUEUE", "exclusive"],
["STEP_CONSOLIDATION_REPAIR_QUEUE", "exclusive"],
["CUMULATIVE_PR_REDERIVE_QUEUE", "exclusive"],
["MEAN_CONSOLIDATION_QUEUE", "exclusive"],
["DENSE_INTRADAY_RETENTION_QUEUE", "exclusive"],
["DENSE_INTRADAY_HOURLY_REBUILD_QUEUE", "exclusive"],
["rollup-full-backfill", "exclusive"],
["mood-rollup-full-backfill", "exclusive"],
["step-consolidation", "exclusive"],
["step-consolidation-repair", "exclusive"],
["cumulative-pr-rederive", "exclusive"],
["mean-consolidation", "exclusive"],
["dense-intraday-retention", "exclusive"],
["dense-intraday-hourly-rebuild", "exclusive"],
])("%s is %s", (queue, policy) => {
expectPolicy(source, queue, policy);
expectPolicy("rollup", queue, policy);
});
});
describe("status registrar queue policies", () => {
const source = read("register-status.ts");
it("MORNING_DIGEST_REFRESH_QUEUE is exclusive", () => {
describe("status group queue policies", () => {
it("morning-digest-refresh is exclusive", () => {
// Seven sleep-write seams enqueue the same user+localDate key. Without
// this the at-most-once-per-morning contract does not hold and the failure
// path re-triggers forced comprehensive generations against the provider.
expectPolicy(source, "MORNING_DIGEST_REFRESH_QUEUE", "exclusive");
expectPolicy("status", "morning-digest-refresh", "exclusive");
});
});
describe("integration-sync registrar queue policies", () => {
const source = read("register-integration-sync.ts");
describe("integration-sync group queue policies", () => {
it.each([
["WHOOP_BACKFILL_QUEUE", "exclusive"],
["FITBIT_BACKFILL_QUEUE", "exclusive"],
["GOOGLE_HEALTH_BACKFILL_QUEUE", "exclusive"],
["STRAVA_BACKFILL_QUEUE", "exclusive"],
["OURA_BACKFILL_QUEUE", "exclusive"],
["GOOGLE_HEALTH_SLEEP_REPAIR_QUEUE", "exclusive"],
["FITBIT_SLEEP_REPAIR_QUEUE", "exclusive"],
["SLEEP_TIMELINE_BACKFILL_QUEUE", "exclusive"],
["LAB_BIOMARKER_BACKFILL_QUEUE", "exclusive"],
["whoop-backfill", "exclusive"],
["fitbit-backfill", "exclusive"],
["google-health-backfill", "exclusive"],
["strava-backfill", "exclusive"],
["oura-backfill", "exclusive"],
["google-health-sleep-repair", "exclusive"],
["fitbit-sleep-repair", "exclusive"],
["sleep-timeline-backfill", "exclusive"],
["lab-biomarker-backfill", "exclusive"],
])("%s is %s", (queue, policy) => {
expectPolicy(source, queue, policy);
expectPolicy("integration_sync", queue, policy);
});
});
describe("maintenance registrar queue policies", () => {
const source = read("register-maintenance.ts");
describe("maintenance group queue policies", () => {
it.each([
["INTAKE_SLOT_DEDUP_QUEUE", "exclusive"],
["NOTE_ENCRYPTION_BACKFILL_QUEUE", "exclusive"],
["MED_NOTES_ENCRYPTION_BACKFILL_QUEUE", "exclusive"],
["FREE_TEXT_ENCRYPTION_BACKFILL_QUEUE", "exclusive"],
["DOCUMENT_THUMBNAIL_BACKFILL_QUEUE", "exclusive"],
["CONTENT_INDEX_BACKFILL_QUEUE", "exclusive"],
["ENCRYPTION_KEY_ROTATE_QUEUE", "exclusive"],
["intake-slot-dedup", "exclusive"],
["note-encryption-backfill", "exclusive"],
["med-notes-encryption-backfill", "exclusive"],
["free-text-encryption-backfill", "exclusive"],
["document-thumbnail-backfill", "exclusive"],
["document-content-index-backfill", "exclusive"],
["encryption-key-rotate", "exclusive"],
])("%s is %s", (queue, policy) => {
expectPolicy(source, queue, policy);
expectPolicy("maintenance", queue, policy);
});
// Per-document queues have no discovery pass to re-converge, so a re-process
// requested after the running job read the old bytes must be admitted.
it.each([
["DOCUMENT_INDEX_QUEUE", "short"],
["DOCUMENT_THUMBNAIL_QUEUE", "short"],
["DOCUMENT_SUMMARY_QUEUE", "short"],
["document-index", "short"],
["document-thumbnail", "short"],
["document-summary", "short"],
])("%s is %s", (queue, policy) => {
expectPolicy(source, queue, policy);
expectPolicy("maintenance", queue, policy);
});
it("ENVIRONMENT_FETCH_QUEUE is deliberately left on standard", () => {
it("environment-fetch is deliberately left on standard", () => {
// Its explicit-range backfill sends with no key at all, on purpose, so any
// policy would collapse two different requested date ranges onto the shared
// empty key. This asserts the omission is intentional and documented, so a
// future reader does not "complete" the table and silently merge them.
const table = source.match(
/const queuePolicies: QueuePolicyTable = \{([\s\S]*?)\n\};/,
);
expect(table![1]).not.toMatch(/\[ENVIRONMENT_FETCH_QUEUE\]/);
expect(source).toMatch(/ENVIRONMENT_FETCH_QUEUE is LEFT ALONE/);
// empty key. A future reader must not "complete" the declaration and
// silently merge them.
expect(groupOf("environment-fetch").name).toBe("maintenance");
expect(jobOf("environment-fetch").policy).toBeUndefined();
});
});
describe("every policy decision carries a reason", () => {
it.each([
"register-rollup.ts",
"register-status.ts",
"register-integration-sync.ts",
"register-maintenance.ts",
])("%s", (file) => {
const table = read(file).match(
/const queuePolicies: QueuePolicyTable = \{([\s\S]*?)\n\};/,
);
expect(table).not.toBeNull();
const policies = table![1].match(/policy:\s*"(?:short|exclusive)"/g) ?? [];
const reasons = table![1].match(/reason:\s*\n?\s*"/g) ?? [];
expect(policies.length).toBeGreaterThan(0);
// A policy without a stated reason is the thing this whole change exists
// to prevent: the next person re-deriving intent from the call sites.
expect(reasons.length).toBe(policies.length);
});
it.each(["rollup", "status", "integration_sync", "maintenance"] as const)(
"%s",
(name) => {
const decided = groupNamed(name).jobs.filter((job) => job.policy);
expect(decided.length).toBeGreaterThan(0);
// A policy without a stated reason is the thing this whole change exists
// to prevent: the next person re-deriving intent from the call sites.
for (const job of decided) {
expect(job.policy!.reason.trim(), job.queue).not.toBe("");
}
},
);
});
@@ -2,72 +2,65 @@
* Dead-queue guard for the two queues that took the writes off the read paths.
*
* Moving a write off a GET only works if the new owner actually runs. A queue
* that is not in `allQueues` is never provisioned, a schedule without a
* `createAndWork` binding drains into nothing, and either failure is silent:
* the worker registry does not declare is never provisioned, a schedule
* without a bound handler drains into nothing, and either failure is silent:
* the cron fires, the job vanishes, and the only symptom is a forecast that
* stops moving or an unlock date that is never pinned. Both look exactly like
* "nothing to do".
*
* Source-text assertions over the registrar, matching the sibling
* `*-queue.test.ts` guards.
* Reads the registry declarations, matching the sibling `*-queue.test.ts`
* guards.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { handleAchievementUnlockSweep } from "@/lib/jobs/achievement-unlock-sweep";
import {
ACHIEVEMENT_UNLOCK_SWEEP_CRON,
ACHIEVEMENT_UNLOCK_SWEEP_QUEUE,
} from "@/lib/jobs/achievement-unlock-sweep-queue";
import {
CYCLE_PREDICTION_REFRESH_CRON,
CYCLE_PREDICTION_REFRESH_QUEUE,
handleCyclePredictionRefresh,
} from "@/lib/jobs/cycle-prediction-refresh";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import { SUBSYSTEM_SURFACE } from "@/lib/jobs/subsystem-surface";
const registrar = readFileSync(
join(__dirname, "..", "reminder", "register-maintenance.ts"),
"utf8",
);
const allQueues = registrar.match(/const allQueues\s*=\s*\[([\s\S]*?)\n\];/);
const QUEUES = [
{
label: "cycle-prediction-refresh",
binding: "CYCLE_PREDICTION_REFRESH_QUEUE",
cron: "CYCLE_PREDICTION_REFRESH_CRON",
handler: "handleCyclePredictionRefresh",
queue: CYCLE_PREDICTION_REFRESH_QUEUE,
cron: CYCLE_PREDICTION_REFRESH_CRON,
handler: handleCyclePredictionRefresh,
name: "cycle-prediction-refresh",
},
{
label: "achievement-unlock-sweep",
binding: "ACHIEVEMENT_UNLOCK_SWEEP_QUEUE",
cron: "ACHIEVEMENT_UNLOCK_SWEEP_CRON",
handler: "handleAchievementUnlockSweep",
queue: ACHIEVEMENT_UNLOCK_SWEEP_QUEUE,
cron: ACHIEVEMENT_UNLOCK_SWEEP_CRON,
handler: handleAchievementUnlockSweep,
name: "achievement-unlock-sweep",
},
];
describe("queues that own a write moved off a GET", () => {
it("extracted an allQueues list to assert against", () => {
// The regexes below all read from this capture; if it ever stops
// matching, every assertion in this file would pass on an empty string.
expect(allQueues).not.toBeNull();
expect(allQueues![1].length).toBeGreaterThan(0);
});
for (const queue of QUEUES) {
describe(queue.label, () => {
it("is provisioned in the allQueues list", () => {
expect(allQueues![1]).toMatch(new RegExp(`\\b${queue.binding}\\b`));
it("is declared in the maintenance group", () => {
expect(queue.queue).toBe(queue.name);
expect(groupOf(queue.queue).name).toBe("maintenance");
});
it("carries a cron schedule", () => {
expect(registrar).toMatch(
new RegExp(`\\[\\s*${queue.binding},\\s*${queue.cron}`),
);
expect(jobOf(queue.queue).cron).toBe(queue.cron);
});
it("binds a createAndWork handler that drains it", () => {
expect(registrar).toMatch(
new RegExp(
`createAndWork[\\s\\S]{0,200}${queue.binding}[\\s\\S]{0,200}${queue.handler}`,
),
);
it("binds the handler that drains it", async () => {
expect(await loadedHandler(queue.queue)).toBe(queue.handler);
});
it("declares a failure audience", () => {
@@ -1,10 +1,10 @@
/**
* v1.10.0 — computed scores (WX-C). Recovery-score queue registration guard.
* Computed scores: recovery-score queue registration guard.
*
* Same source-text-grep approach as the other queue-wiring guards: assert
* the queue is imported, registered in `allQueues`, scheduled, and wired to
* a `createAndWork` handler — without booting pg-boss + Prisma. An unregistered
* queue silently never drains (the recurring past bug this guards against).
* Reads the worker registry: the queue is declared in the status group,
* scheduled on its cron, and bound to the handler that runs the pass. An
* unregistered queue silently never drains (the recurring past bug this
* guards against).
*
* Also pins the iOS-write rejection of the server-owned COMPUTED source on
* the batch ingest route: the `batchSourceEnum` allowlist must stay exactly
@@ -16,11 +16,16 @@ import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the queue wiring moved out of the 2143-LOC reminder-worker boot
// file into domain registrars; the recovery-score queue lives in the status
// registrar. The dead-queue guard follows the wiring there.
const REGISTRAR_PATH = join(__dirname, "..", "reminder", "register-status.ts");
const workerSource = readFileSync(REGISTRAR_PATH, "utf8");
import {
RECOVERY_SCORE_CRON,
RECOVERY_SCORE_QUEUE,
} from "@/lib/jobs/recovery-score-queue";
import { handleRecoveryScore } from "@/lib/jobs/reminder/status-handlers";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const BATCH_ROUTE_PATH = join(
__dirname,
@@ -35,32 +40,19 @@ const BATCH_ROUTE_PATH = join(
);
const batchSource = readFileSync(BATCH_ROUTE_PATH, "utf8");
describe("reminder-worker — recovery-score wiring", () => {
it("imports the queue symbols from the recovery-score module", () => {
expect(workerSource).toMatch(/from\s*["']@\/lib\/jobs\/recovery-score["']/);
expect(workerSource).toMatch(/\bRECOVERY_SCORE_QUEUE\b/);
expect(workerSource).toMatch(/\bRECOVERY_SCORE_CRON\b/);
expect(workerSource).toMatch(/\brunRecoveryScore\b/);
});
it("registers the recovery-score queue in the allQueues loop", () => {
const allQueuesMatch = workerSource.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bRECOVERY_SCORE_QUEUE\b/);
describe("recovery-score registration", () => {
it("declares the recovery-score queue in the status group", () => {
expect(groupOf(RECOVERY_SCORE_QUEUE).name).toBe("status");
});
it("schedules the recovery-score cron", () => {
expect(workerSource).toMatch(
/\[RECOVERY_SCORE_QUEUE,\s*RECOVERY_SCORE_CRON\]/,
);
const job = jobOf(RECOVERY_SCORE_QUEUE);
expect(job.cron).toBe(RECOVERY_SCORE_CRON);
expect(job.work).toEqual({ localConcurrency: 1 });
});
it("registers a createAndWork handler that runs the recovery-score pass", () => {
expect(workerSource).toMatch(
/createAndWork[\s\S]{0,200}RECOVERY_SCORE_QUEUE[\s\S]{0,400}runRecoveryScore/,
);
it("binds the handler that runs the recovery-score pass", async () => {
expect(await loadedHandler(RECOVERY_SCORE_QUEUE)).toBe(handleRecoveryScore);
});
});
+2 -3
View File
@@ -2,13 +2,12 @@ import type { Readable } from "node:stream";
import { describe, it, expect, beforeEach, vi } from "vitest";
import { _resetCryptoCacheForTests, encrypt } from "@/lib/crypto";
import { encryptBackup } from "../offhost-backup";
import { handleRestoreDrill, runRestoreDrill } from "../restore-drill";
import {
handleRestoreDrill,
runRestoreDrill,
RESTORE_DRILL_CRON,
RESTORE_DRILL_QUEUE,
RESTORE_DRILL_SEND_OPTIONS,
} from "../restore-drill";
} from "@/lib/jobs/restore-drill-queue";
import { QUEUE_RUNTIME } from "../queue-runtime";
vi.mock("../report-worker-error", () => ({
@@ -1,50 +1,60 @@
/**
* v1.17.1 — pg-boss queue registration for the one-shot sleep-timeline backfill.
* pg-boss queue registration for the one-shot sleep-timeline backfill.
*
* The reminder-worker imports heavy infrastructure, so we read the source as
* text and assert the queue constant is declared, registered in `allQueues`,
* wired to a `createAndWork` handler, and enqueued at boot — the fast guard that
* catches the v1.4.37 dead-queue class (a queue declared but never registered,
* which silently never drains).
* Reads the worker registry: the source queue is declared in the
* integration-sync group, forwards each job to the shared admission queue,
* and is offered again by the boot discovery. This is the fast guard for the
* dead-queue class (a queue declared but never registered, which silently
* never drains).
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the queue wiring + boot discovery moved out of the 2143-LOC
// reminder-worker boot file into domain registrars; the sleep-timeline backfill
// lives in the integration-sync registrar. The dead-queue guard follows it.
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-integration-sync.ts",
);
const source = readFileSync(REGISTRAR_PATH, "utf8");
import { INTEGRATION_BACKFILL_ADMISSION_QUEUE } from "@/lib/jobs/integration-backfill-admission";
import {
discoveryOf,
groupOf,
jobOf,
recordingBoss,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import {
SLEEP_TIMELINE_BACKFILL_CONCURRENCY,
SLEEP_TIMELINE_BACKFILL_QUEUE,
} from "@/lib/jobs/sleep-timeline-backfill-queue";
describe("reminder-worker — sleep-timeline backfill queue", () => {
it("registers the queue in allQueues (v1.4.37 dead-queue guard)", () => {
const block = /const allQueues = \[([\s\S]*?)\];/.exec(source);
expect(block).not.toBeNull();
expect(block![1]!).toContain("SLEEP_TIMELINE_BACKFILL_QUEUE");
describe("sleep-timeline backfill registration", () => {
it("declares the queue in the integration-sync group", () => {
expect(groupOf(SLEEP_TIMELINE_BACKFILL_QUEUE).name).toBe(
"integration_sync",
);
const job = jobOf(SLEEP_TIMELINE_BACKFILL_QUEUE);
expect(job.policy?.policy).toBe("exclusive");
expect(job.work).toEqual({
localConcurrency: SLEEP_TIMELINE_BACKFILL_CONCURRENCY,
});
});
it("wires a createAndWork handler through the shared admission queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,260}SLEEP_TIMELINE_BACKFILL_QUEUE[\s\S]{0,300}enqueueIntegrationBackfillAdmission/,
);
it("forwards each job through the shared admission queue", async () => {
const { boss, sends } = recordingBoss();
const handler = await jobOf(SLEEP_TIMELINE_BACKFILL_QUEUE).load({ boss });
await handler([
{
id: "j1",
name: SLEEP_TIMELINE_BACKFILL_QUEUE,
data: { userId: "u1", provider: "WHOOP" },
expireInSeconds: 60,
heartbeatSeconds: null,
retryCount: 0,
signal: new AbortController().signal,
},
]);
expect(sends).toHaveLength(1);
expect(sends[0]!.queue).toBe(INTEGRATION_BACKFILL_ADMISSION_QUEUE);
expect(sends[0]!.data).toMatchObject({ kind: "sleep-timeline-backfill" });
});
it("wires the boot discovery", () => {
expect(source).toMatch(
/enqueueBootTimeSleepTimelineBackfill\(\s*bootStaggerSecondsFor\("sleep-timeline-backfill"\)/,
);
});
it("imports the backfill exports", () => {
expect(source).toMatch(
/from\s*["']@\/lib\/jobs\/sleep-timeline-backfill["']/,
expect(discoveryOf("integration_sync")).toContain(
"sleep-timeline-backfill",
);
});
});
@@ -1,5 +1,5 @@
/**
* v1.15.20 — unit tests for the shared 02:xx status-cron discovery.
* Unit tests for the shared 02:xx status-cron discovery.
*
* Pins the three gates the discovery applies:
* 1. the operator assistant kill-switch (`insightStatus` flag),
@@ -9,8 +9,8 @@
* 3. the pregenerate-candidate skip (a stale or missing comprehensive
* cache → the 04:30 pass owns the user; the 02:xx crons keep the
* fresh-cache accounts), applied only while `briefing` is on,
* plus the mood-status queue registration in the worker source (the
* v1.4.37 dead-queue class guard).
* plus the nightly status batch's declaration in the worker registry (the
* dead-queue class guard).
*/
import { describe, it, expect, vi, beforeEach } from "vitest";
import fs from "node:fs";
@@ -25,6 +25,15 @@ vi.mock("@/lib/feature-flags", async () =>
import { findStatusCronCandidates } from "../status-cron-candidates";
import { PREGENERATE_STALE_MS } from "../insight-pregenerate";
import { handleGeneralStatusGenerate } from "../reminder/insights-handlers";
import { nightlyInsightPassOptions } from "../reminder/registrar-shared";
import { allJobDefinitions } from "../registry";
import {
groupNamed,
groupOf,
jobOf,
loadedHandler,
} from "../registry/__tests__/registry-helpers";
const NOW = new Date("2026-06-10T02:00:00.000Z");
const STALE_AT = new Date(NOW.getTime() - PREGENERATE_STALE_MS - 60_000);
@@ -176,10 +185,7 @@ describe("findStatusCronCandidates — gates", () => {
});
describe("the nightly status batch is the one status cron", () => {
const workerSrc = fs.readFileSync(
path.resolve(__dirname, "../reminder/register-status.ts"),
"utf8",
);
const GENERAL_STATUS_QUEUE = "insights-general-status";
const RETIRED = [
"insights-blood-pressure-status",
"insights-weight-status",
@@ -189,27 +195,22 @@ describe("the nightly status batch is the one status cron", () => {
"insights-medication-compliance-status",
];
it("registers and schedules the 02:00 batch with the nightly pass options", () => {
const match = workerSrc.match(/const allQueues\s*=\s*\[([\s\S]*?)\];/);
expect(match).not.toBeNull();
expect(match![1]).toMatch(/\bGENERAL_STATUS_QUEUE\b/);
expect(workerSrc).toMatch(
/\[\s*GENERAL_STATUS_QUEUE\s*,\s*GENERAL_STATUS_CRON\s*,\s*nightlyInsightPassOptions\s*\]/,
it("registers and schedules the 02:00 batch with the nightly pass options", async () => {
expect(groupOf(GENERAL_STATUS_QUEUE).name).toBe("status");
const job = jobOf(GENERAL_STATUS_QUEUE);
expect(job.cron).toBe("0 2 * * *");
expect(job.scheduleOptions).toBe(nightlyInsightPassOptions);
expect(await loadedHandler(GENERAL_STATUS_QUEUE)).toBe(
handleGeneralStatusGenerate,
);
expect(workerSrc).toMatch(/GENERAL_STATUS_CRON\s*=\s*"0 2 \* \* \*"/);
});
it("retires the six per-metric crons on an instance that still has them", () => {
const retired = workerSrc.match(
/const RETIRED_STATUS_QUEUES\s*=\s*\[([\s\S]*?)\]/,
expect([...(groupNamed("status").retired ?? [])].sort()).toEqual(
[...RETIRED].sort(),
);
expect(retired).not.toBeNull();
for (const name of RETIRED) expect(retired![1]).toContain(`"${name}"`);
expect(workerSrc).toMatch(
/await retireQueues\(boss,\s*RETIRED_STATUS_QUEUES\)/,
);
const live = workerSrc.replace(retired![0], "");
for (const name of RETIRED) expect(live).not.toContain(`"${name}"`);
const live = allJobDefinitions().map((job) => job.queue);
for (const name of RETIRED) expect(live).not.toContain(name);
});
it("drives the batch through the shared discovery", () => {
@@ -1,51 +1,46 @@
/**
* v1.28.37 — step-consolidation repair queue registration guard +
* `extractStatsDay` unit coverage.
* Step-consolidation repair queue registration guard + `extractStatsDay`
* unit coverage.
*
* Same source-text-grep approach as the drain + step-consolidation +
* mean-consolidation guards: assert the repair queue is registered in
* `allQueues`, a `createAndWork` handler is wired against it, and the
* boot-discovery enqueue helper fires — without booting pg-boss + Prisma.
* An unregistered queue silently never drains (the dead-queue class).
* Reads the worker registry: the repair queue is declared in the rollup
* group, bound to the repair handler, and offered again by the rollup boot
* discovery. An unregistered queue silently never drains (the dead-queue
* class).
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import { extractStatsDay } from "../step-consolidation-repair";
import { handleStepConsolidationRepair } from "@/lib/jobs/reminder/rollup-handlers";
import {
discoveryOf,
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
const REGISTRAR_PATH = join(__dirname, "..", "reminder", "register-rollup.ts");
const source = readFileSync(REGISTRAR_PATH, "utf8");
import {
STEP_CONSOLIDATION_REPAIR_CONCURRENCY,
STEP_CONSOLIDATION_REPAIR_QUEUE,
extractStatsDay,
} from "../step-consolidation-repair";
describe("reminder-worker — step-consolidation-repair wiring", () => {
it("imports the queue symbols from the repair module", () => {
expect(source).toMatch(
/from\s*["']@\/lib\/jobs\/step-consolidation-repair["']/,
);
expect(source).toMatch(/\bSTEP_CONSOLIDATION_REPAIR_QUEUE\b/);
expect(source).toMatch(/\brunStepConsolidationRepairForUser\b/);
expect(source).toMatch(/\benqueueBootTimeStepConsolidationRepair\b/);
describe("step-consolidation-repair registration", () => {
it("declares the repair queue in the rollup group", () => {
expect(groupOf(STEP_CONSOLIDATION_REPAIR_QUEUE).name).toBe("rollup");
const job = jobOf(STEP_CONSOLIDATION_REPAIR_QUEUE);
expect(job.policy?.policy).toBe("exclusive");
expect(job.work).toEqual({
localConcurrency: STEP_CONSOLIDATION_REPAIR_CONCURRENCY,
});
});
it("registers the repair queue in the allQueues loop", () => {
const allQueuesMatch = source.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bSTEP_CONSOLIDATION_REPAIR_QUEUE\b/);
});
it("registers a createAndWork handler against the repair queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}STEP_CONSOLIDATION_REPAIR_QUEUE[\s\S]{0,400}runStepConsolidationRepairForUser/,
it("binds the repair handler", async () => {
expect(await loadedHandler(STEP_CONSOLIDATION_REPAIR_QUEUE)).toBe(
handleStepConsolidationRepair,
);
});
it("fires the boot-discovery enqueue helper", () => {
expect(source).toMatch(
/await enqueueBootTimeStepConsolidationRepair\([^)]*\)/,
);
it("runs the boot discovery pass", () => {
expect(discoveryOf("rollup")).toContain("step-consolidation-repair");
});
});
@@ -1,39 +1,125 @@
/**
* v1.10.0 — computed scores (WX-E). Queue-registration guards for the
* Stress + Strain nightly score jobs and the dense intra-day retention
* drain.
* Computed scores: queue-registration guards for the Stress + Strain nightly
* score jobs and the dense intra-day retention drain.
*
* Same source-text-grep approach as the WX-C recovery-score guard: assert
* each queue is imported, registered in `allQueues`, scheduled (the two
* cron-driven score jobs), and wired to a `createAndWork` handler — without
* booting pg-boss + Prisma. An unregistered queue silently never drains
* (the recurring past bug this guards against).
* Reads the worker registry: each queue is declared in its group, scheduled
* (the two cron-driven score jobs), bound to its handler, and offered again by
* boot discovery where it has one — without booting pg-boss + Prisma. An
* unregistered queue silently never drains (the recurring past bug this
* guards against). The retention handler and the nightly drain run against
* mocked passes.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
import type { Job } from "pg-boss";
import { describe, expect, it, vi } from "vitest";
// v1.18.1 — the queue wiring moved out of the 2143-LOC reminder-worker boot
// file into domain registrars. Stress + Strain live in the status registrar;
// the dense intra-day retention queue (and the nightly fold onto the
// drain-cumulative tick) lives in the rollup registrar. Concatenate both so the
// dead-queue guard follows the wiring into each.
const STATUS_REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-status.ts",
const {
runDenseIntradayRetention,
runDenseIntradayRetentionForUser,
workerLog,
} = vi.hoisted(() => ({
runDenseIntradayRetention: vi.fn(async () => ({
stoppedEarly: false,
totals: {
usersScanned: 0,
daysConsolidated: 0,
perSampleRowsSoftDeleted: 0,
hourlyRowsUpserted: 0,
dailyRowsRetired: 0,
},
})),
runDenseIntradayRetentionForUser: vi.fn(async () => ({
daysConsolidated: 1,
perSampleRowsSoftDeleted: 2,
derivedRestingRowsUpserted: 3,
stoppedEarly: false,
})),
workerLog: vi.fn(),
}));
vi.mock(
"@/lib/measurements/dense-intraday-retention",
async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/measurements/dense-intraday-retention")
>()),
runDenseIntradayRetention,
}),
);
const ROLLUP_REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-rollup.ts",
vi.mock("@/lib/jobs/dense-intraday-retention", async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/jobs/dense-intraday-retention")
>()),
runDenseIntradayRetentionForUser,
}));
vi.mock(
"@/lib/measurements/drain-per-sample-cumulative",
async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/measurements/drain-per-sample-cumulative")
>()),
drainPerSampleCumulative: vi.fn(async () => ({
stoppedEarly: false,
totals: {
usersScanned: 0,
bucketsCollapsed: 0,
perSampleRowsDeleted: 0,
dailyRowsUpserted: 0,
},
})),
}),
);
const source =
readFileSync(STATUS_REGISTRAR_PATH, "utf8") +
readFileSync(ROLLUP_REGISTRAR_PATH, "utf8");
vi.mock(
"@/lib/measurements/consolidate-daily-mean",
async (importOriginal) => ({
...(await importOriginal<
typeof import("@/lib/measurements/consolidate-daily-mean")
>()),
consolidateDailyMean: vi.fn(async () => ({
stoppedEarly: false,
totals: {
usersScanned: 0,
daysConsolidated: 0,
perSampleRowsSoftDeleted: 0,
dailyRowsUpserted: 0,
daysFailed: 0,
},
})),
}),
);
vi.mock("@/lib/jobs/reminder/shared", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/jobs/reminder/shared")>()),
getWorkerPrisma: () => ({}),
workerLog,
}));
import { DENSE_INTRADAY_HOURLY_REBUILD_QUEUE } from "@/lib/jobs/dense-intraday-hourly-rebuild-queue";
import { DENSE_INTRADAY_RETENTION_QUEUE } from "@/lib/jobs/dense-intraday-retention-queue";
import { DRAIN_CUMULATIVE_QUEUE } from "@/lib/jobs/drain-cumulative-queue";
import {
handleDenseIntradayHourlyRebuild,
handleDenseIntradayRetention,
} from "@/lib/jobs/reminder/rollup-handlers";
import {
handleStrainScore,
handleStressScore,
} from "@/lib/jobs/reminder/status-handlers";
import {
discoveryOf,
groupOf,
jobOf,
loadedHandler,
recordingBoss,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import {
STRAIN_SCORE_CRON,
STRAIN_SCORE_QUEUE,
} from "@/lib/jobs/strain-score-queue";
import {
STRESS_SCORE_CRON,
STRESS_SCORE_QUEUE,
} from "@/lib/jobs/stress-score-queue";
const DENSE_RETENTION_PATH = join(
__dirname,
@@ -42,91 +128,70 @@ const DENSE_RETENTION_PATH = join(
);
const denseRetentionSource = readFileSync(DENSE_RETENTION_PATH, "utf8");
function allQueuesBlock(): string {
// Each registrar owns its own `const allQueues = [...]`; union every block so
// a queue registered in either the status or rollup registrar satisfies the
// membership assertion.
const blocks = [...source.matchAll(/const allQueues\s*=\s*\[([\s\S]*?)\];/g)];
expect(blocks.length).toBeGreaterThan(0);
return blocks.map((m) => m[1]).join("\n");
function job<T>(name: string, data: T): Job<T> {
return {
id: "j1",
name,
data,
expireInSeconds: 900,
heartbeatSeconds: null,
retryCount: 0,
signal: new AbortController().signal,
};
}
describe("reminder-worker — stress-score wiring", () => {
it("imports the queue symbols from the stress-score module", () => {
expect(source).toMatch(/from\s*["']@\/lib\/jobs\/stress-score["']/);
expect(source).toMatch(/\bSTRESS_SCORE_QUEUE\b/);
expect(source).toMatch(/\bSTRESS_SCORE_CRON\b/);
expect(source).toMatch(/\brunStressScore\b/);
});
it("registers the stress-score queue in allQueues", () => {
expect(allQueuesBlock()).toMatch(/\bSTRESS_SCORE_QUEUE\b/);
describe("stress-score registration", () => {
it("declares the stress-score queue in the status group", () => {
expect(groupOf(STRESS_SCORE_QUEUE).name).toBe("status");
});
it("schedules the stress-score cron", () => {
expect(source).toMatch(/\[STRESS_SCORE_QUEUE,\s*STRESS_SCORE_CRON\]/);
expect(jobOf(STRESS_SCORE_QUEUE).cron).toBe(STRESS_SCORE_CRON);
});
it("registers a createAndWork handler that runs the stress-score pass", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}STRESS_SCORE_QUEUE[\s\S]{0,400}runStressScore/,
);
it("binds the handler that runs the stress-score pass", async () => {
expect(await loadedHandler(STRESS_SCORE_QUEUE)).toBe(handleStressScore);
});
});
describe("reminder-worker — strain-score wiring", () => {
it("imports the queue symbols from the strain-score module", () => {
expect(source).toMatch(/from\s*["']@\/lib\/jobs\/strain-score["']/);
expect(source).toMatch(/\bSTRAIN_SCORE_QUEUE\b/);
expect(source).toMatch(/\bSTRAIN_SCORE_CRON\b/);
expect(source).toMatch(/\brunStrainScore\b/);
});
it("registers the strain-score queue in allQueues", () => {
expect(allQueuesBlock()).toMatch(/\bSTRAIN_SCORE_QUEUE\b/);
describe("strain-score registration", () => {
it("declares the strain-score queue in the status group", () => {
expect(groupOf(STRAIN_SCORE_QUEUE).name).toBe("status");
});
it("schedules the strain-score cron", () => {
expect(source).toMatch(/\[STRAIN_SCORE_QUEUE,\s*STRAIN_SCORE_CRON\]/);
expect(jobOf(STRAIN_SCORE_QUEUE).cron).toBe(STRAIN_SCORE_CRON);
});
it("registers a createAndWork handler that runs the strain-score pass", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,200}STRAIN_SCORE_QUEUE[\s\S]{0,400}runStrainScore/,
);
it("binds the handler that runs the strain-score pass", async () => {
expect(await loadedHandler(STRAIN_SCORE_QUEUE)).toBe(handleStrainScore);
});
});
describe("reminder-worker — dense intra-day retention wiring", () => {
it("imports the queue symbols from the dense-intraday-retention module", () => {
expect(source).toMatch(
/from\s*["']@\/lib\/jobs\/dense-intraday-retention["']/,
);
expect(source).toMatch(/\bDENSE_INTRADAY_RETENTION_QUEUE\b/);
expect(source).toMatch(/\brunDenseIntradayRetentionForUser\b/);
expect(source).toMatch(/\benqueueBootTimeDenseIntradayRetention\b/);
});
it("registers the dense-intraday-retention queue in allQueues", () => {
expect(allQueuesBlock()).toMatch(/\bDENSE_INTRADAY_RETENTION_QUEUE\b/);
});
it("registers a createAndWork handler against the dense-intraday-retention queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,250}DENSE_INTRADAY_RETENTION_QUEUE[\s\S]{0,500}runDenseIntradayRetentionForUser/,
describe("dense intra-day retention registration", () => {
it("declares the dense-intraday-retention queue in the rollup group", () => {
expect(groupOf(DENSE_INTRADAY_RETENTION_QUEUE).name).toBe("rollup");
expect(jobOf(DENSE_INTRADAY_RETENTION_QUEUE).policy?.policy).toBe(
"exclusive",
);
});
it("fires the boot-discovery enqueue helper", () => {
expect(source).toMatch(/await enqueueBootTimeDenseIntradayRetention\(\)/);
it("binds the per-user retention handler", async () => {
expect(await loadedHandler(DENSE_INTRADAY_RETENTION_QUEUE)).toBe(
handleDenseIntradayRetention,
);
});
it("fires the boot-discovery pass", () => {
expect(discoveryOf("rollup")).toContain("dense-intraday-retention");
});
it("defers the boot-discovery drain past the startup storm (P2028 guard)", () => {
// The boot-time per-user retention jobs MUST carry a startAfter delay so
// the transaction-per-day drain never contends with the deploy storm +
// migration + other boot backfills + foreground health-checks for the
// shared connection pool. Dropping this re-opens the v1.10.0 pool-
// exhaustion (P2028) restart loop on a data-heavy tenant.
// shared connection pool. Dropping this re-opens the pool-exhaustion
// (P2028) restart loop on a data-heavy tenant.
expect(denseRetentionSource).toMatch(
/DENSE_INTRADAY_RETENTION_BOOT_DELAY_SECONDS\s*=\s*\d+/,
);
@@ -135,44 +200,52 @@ describe("reminder-worker — dense intra-day retention wiring", () => {
);
});
it("folds the nightly dense-retention walk onto the drain-cumulative tick", () => {
it("folds the nightly dense-retention walk onto the drain-cumulative tick", async () => {
// The steady-state nightly walk runs the global (no-userId) pass on the
// same tick as the cumulative + mean drains.
expect(source).toMatch(/runDenseIntradayRetention\(\s*getWorkerPrisma\(\)/);
const { boss } = recordingBoss();
const drain = await jobOf(DRAIN_CUMULATIVE_QUEUE).load({ boss });
await drain([job(DRAIN_CUMULATIVE_QUEUE, {})]);
expect(runDenseIntradayRetention).toHaveBeenCalledTimes(1);
expect(runDenseIntradayRetention).toHaveBeenCalledWith(
expect.anything(),
expect.objectContaining({ dryRun: false }),
);
});
it("surfaces the iOS#34 derived-resting count in the per-user worker log", () => {
it("surfaces the iOS#34 derived-resting count in the per-user worker log", async () => {
// The PULSE facet mints derived RESTING_HEART_RATE rows for proxy users;
// the per-user handler returns the count and the worker logs it so the
// signal-preservation is observable in production.
expect(source).toMatch(/derivedRestingRowsUpserted/);
const outcome = await handleDenseIntradayRetention([
job(DENSE_INTRADAY_RETENTION_QUEUE, {
userId: "u1",
enqueuedAt: "2026-01-01T00:00:00.000Z",
}),
]);
expect(outcome).toMatchObject({
did: { derived_resting_rows_upserted: 3 },
});
expect(workerLog).toHaveBeenCalledWith(
"info",
expect.stringContaining("derivedRestingRowsUpserted=3"),
);
});
});
describe("reminder-worker — dense hourly-rebuild wiring", () => {
it("imports the queue symbols from the dense-intraday-hourly-rebuild module", () => {
expect(source).toMatch(
/from\s*["']@\/lib\/jobs\/dense-intraday-hourly-rebuild["']/,
);
expect(source).toMatch(/\bDENSE_INTRADAY_HOURLY_REBUILD_QUEUE\b/);
expect(source).toMatch(/\brunDenseIntradayHourlyRebuildForUser\b/);
expect(source).toMatch(/\benqueueBootTimeDenseIntradayHourlyRebuild\b/);
describe("dense hourly-rebuild registration", () => {
it("declares the hourly-rebuild queue in the rollup group", () => {
expect(groupOf(DENSE_INTRADAY_HOURLY_REBUILD_QUEUE).name).toBe("rollup");
});
it("registers the hourly-rebuild queue in allQueues", () => {
expect(allQueuesBlock()).toMatch(/\bDENSE_INTRADAY_HOURLY_REBUILD_QUEUE\b/);
});
it("registers a createAndWork handler against the hourly-rebuild queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,250}DENSE_INTRADAY_HOURLY_REBUILD_QUEUE[\s\S]{0,500}runDenseIntradayHourlyRebuildForUser/,
it("binds the hourly-rebuild handler", async () => {
expect(await loadedHandler(DENSE_INTRADAY_HOURLY_REBUILD_QUEUE)).toBe(
handleDenseIntradayHourlyRebuild,
);
});
it("fires the boot-discovery enqueue helper", () => {
expect(source).toMatch(
/await enqueueBootTimeDenseIntradayHourlyRebuild\(\)/,
);
it("fires the boot-discovery pass", () => {
expect(discoveryOf("rollup")).toContain("dense-intraday-hourly-rebuild");
});
it("defers the boot-discovery rebuild past the startup storm (P2028 guard)", () => {
@@ -1,92 +1,17 @@
import { existsSync, readFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
/**
* Every queue the worker registry declares has a failure audience in
* `SUBSYSTEM_SURFACE`, and the table names no queue the registry has dropped.
*/
import { describe, expect, it } from "vitest";
import { allJobDefinitions } from "@/lib/jobs/registry";
import {
SUBSYSTEM_SURFACE,
type SubsystemSurface,
} from "@/lib/jobs/subsystem-surface";
const REGISTRARS = [
"register-integration-sync.ts",
"register-status.ts",
"register-rollup.ts",
"register-reminders.ts",
"register-maintenance.ts",
].map((file) => join(process.cwd(), "src/lib/jobs/reminder", file));
function withoutComments(source: string): string {
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "");
}
export function extractAllQueueBindings(source: string): string[] {
const match = /const\s+allQueues\s*=\s*\[([\s\S]*?)\n\s*\];/.exec(source);
if (!match) throw new Error("Registrar has no allQueues array");
return withoutComments(match[1])
.split(",")
.map((entry) => entry.trim())
.filter(Boolean)
.map((entry) => {
if (!/^[A-Z][A-Z0-9_]*$/.test(entry)) {
throw new Error(`Unsupported allQueues entry: ${entry}`);
}
return entry;
});
}
function literalPattern(binding: string): RegExp {
return new RegExp(
`(?:export\\s+)?const\\s+${binding}(?:\\s*:[^=;]+)?\\s*=\\s*["']([^"']+)["']`,
);
}
function modulePath(fromFile: string, specifier: string): string {
const base = specifier.startsWith("@/")
? resolve(process.cwd(), "src", specifier.slice(2))
: resolve(dirname(fromFile), specifier);
const candidates = [`${base}.ts`, `${base}.tsx`, join(base, "index.ts")];
const found = candidates.find((candidate) => existsSync(candidate));
if (!found) throw new Error(`Cannot resolve ${specifier} from ${fromFile}`);
return found;
}
function resolveQueueBinding(
binding: string,
file: string,
seen: Set<string> = new Set(),
): string {
const visit = `${file}:${binding}`;
if (seen.has(visit)) throw new Error(`Cyclic queue binding: ${visit}`);
seen.add(visit);
const source = withoutComments(readFileSync(file, "utf8"));
const local = literalPattern(binding).exec(source);
if (local) return local[1];
const imports = /import\s*{([\s\S]*?)}\s*from\s*["']([^"']+)["'];?/g;
for (const match of source.matchAll(imports)) {
for (const rawToken of match[1].split(",")) {
const token = rawToken.trim().replace(/^type\s+/, "");
if (!token) continue;
const [imported, alias] = token
.split(/\s+as\s+/)
.map((part) => part.trim());
if ((alias ?? imported) !== binding) continue;
return resolveQueueBinding(imported, modulePath(file, match[2]), seen);
}
}
throw new Error(`Cannot resolve queue binding ${binding} from ${file}`);
}
function registeredQueueNames(): string[] {
return REGISTRARS.flatMap((file) => {
const source = readFileSync(file, "utf8");
return extractAllQueueBindings(source).map((binding) =>
resolveQueueBinding(binding, file),
);
});
return allJobDefinitions().map((job) => job.queue);
}
export function findSurfaceDrift(
@@ -113,8 +38,9 @@ function assertSurfaceComplete(
}
describe("SUBSYSTEM_SURFACE", () => {
it("classifies every current registrar queue with no stale entries", () => {
it("classifies every current registry queue with no stale entries", () => {
const names = registeredQueueNames();
expect(names.length).toBeGreaterThan(0);
expect(new Set(names).size).toBe(names.length);
expect(() => assertSurfaceComplete(names, SUBSYSTEM_SURFACE)).not.toThrow();
});
@@ -126,24 +52,9 @@ describe("SUBSYSTEM_SURFACE", () => {
}
});
it("catches a new allQueues binding without a surface entry", () => {
const fixture = `
const EXISTING_QUEUE = "existing-queue";
const NEW_QUEUE = "new-queue";
const allQueues = [
EXISTING_QUEUE,
NEW_QUEUE,
];
`;
const names = extractAllQueueBindings(fixture).map((binding) => {
const match = literalPattern(binding).exec(withoutComments(fixture));
if (!match)
throw new Error(`Fixture binding did not resolve: ${binding}`);
return match[1];
});
it("catches a new registry queue without a surface entry", () => {
expect(() =>
assertSurfaceComplete(names, {
assertSurfaceComplete(["existing-queue", "new-queue"], {
"existing-queue": { audience: "system" },
}),
).toThrow(/missing=\[new-queue\]/);
@@ -1,63 +1,56 @@
/**
* v1.12.2 ios-coord — TLS-pin-monitor queue registration guard.
* TLS-pin-monitor queue registration guard.
*
* Same source-text-grep approach as the other queue-wiring guards: assert
* the queue is imported, registered in `allQueues`, scheduled, and wired to
* a `createAndWork` handler — without booting pg-boss + Prisma. An unregistered
* queue silently never drains (the recurring v1.4.37 dead-queue bug), which
* would leave the pinned-leaf-rotation alarm dark.
* Reads the worker registry: the queue is declared in the maintenance group,
* scheduled on its cron with the default retries, and bound to the handler
* that runs the monitor pass. An unregistered queue silently never drains
* (the dead-queue class), which would leave the pinned-leaf-rotation alarm
* dark.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the TLS-pin-monitor wiring moved out of the 2143-LOC
// reminder-worker boot file into the maintenance registrar. The dead-queue
// guard follows the wiring there (the ops-handlers module is still concatenated
// for the handler-body `runTlsPinMonitor` assertion).
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-maintenance.ts",
import { handleTlsPinMonitor } from "@/lib/jobs/reminder/ops-handlers";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import {
TLS_PIN_MONITOR_CRON,
TLS_PIN_MONITOR_QUEUE,
} from "@/lib/jobs/tls-pin-monitor-queue";
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "ops-handlers.ts"),
"utf8",
);
const workerSource =
readFileSync(REGISTRAR_PATH, "utf8") +
readFileSync(join(__dirname, "..", "reminder", "ops-handlers.ts"), "utf8");
describe("reminder-worker — tls-pin-monitor wiring", () => {
it("imports the queue symbols from the tls-pin-monitor module", () => {
expect(workerSource).toMatch(
/from\s*["']@\/lib\/jobs\/tls-pin-monitor["']/,
);
expect(workerSource).toMatch(/\bTLS_PIN_MONITOR_QUEUE\b/);
expect(workerSource).toMatch(/\bTLS_PIN_MONITOR_CRON\b/);
expect(workerSource).toMatch(/\brunTlsPinMonitor\b/);
describe("tls-pin-monitor registration", () => {
it("imports the monitor pass into the ops handlers", () => {
expect(handlerSource).toMatch(/\brunTlsPinMonitor\b/);
});
it("registers the tls-pin-monitor queue in the allQueues loop", () => {
const allQueuesMatch = workerSource.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueuesMatch).not.toBeNull();
expect(allQueuesMatch![1]).toMatch(/\bTLS_PIN_MONITOR_QUEUE\b/);
it("declares the tls-pin-monitor queue in the maintenance group", () => {
expect(groupOf(TLS_PIN_MONITOR_QUEUE).name).toBe("maintenance");
});
it("schedules the tls-pin-monitor cron", () => {
expect(workerSource).toMatch(
/\[TLS_PIN_MONITOR_QUEUE,\s*TLS_PIN_MONITOR_CRON\]/,
);
it("schedules the tls-pin-monitor cron with the default retries", () => {
const job = jobOf(TLS_PIN_MONITOR_QUEUE);
expect(job.cron).toBe(TLS_PIN_MONITOR_CRON);
expect(job.scheduleOptions).toBeUndefined();
});
it("registers a createAndWork handler for the queue", () => {
expect(workerSource).toMatch(
/createAndWork[\s\S]{0,200}TLS_PIN_MONITOR_QUEUE[\s\S]{0,200}handleTlsPinMonitor/,
it("binds the monitor handler", async () => {
expect(await loadedHandler(TLS_PIN_MONITOR_QUEUE)).toBe(
handleTlsPinMonitor,
);
});
it("runs the monitor pass inside the handler", () => {
expect(workerSource).toMatch(
expect(handlerSource).toMatch(
/handleTlsPinMonitor[\s\S]{0,400}runTlsPinMonitor/,
);
});
+93 -80
View File
@@ -1,113 +1,126 @@
/**
* v1.11.0 — pg-boss queue registration for the WHOOP sync layer.
* pg-boss queue registration for the WHOOP sync layer.
*
* The reminder-worker imports heavy infrastructure (pg-boss, Prisma adapter,
* notification dispatchers, …) so we don't boot the whole worker here. Instead
* we read the source as text and assert the queue constants + cron schedules +
* handler wiring + allQueues + boot-enqueue are present — the fast guard that
* catches the v1.4.37 dead-queue class (a queue declared but never registered
* in `allQueues`, which silently never drains).
* Reads the worker registry instead of booting the worker: every WHOOP queue
* is declared in the integration-sync group with its cron and handler, the
* backfill forwards to the shared admission lane, and the boot discovery
* offers it again. This is the fast guard for the dead-queue class (a queue
* declared but never registered, which silently never drains).
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the queue wiring + boot discovery moved out of the 2143-LOC
// reminder-worker boot file into domain registrars; the WHOOP queues live in
// the integration-sync registrar. The dead-queue guard follows the wiring there
// (the handler module is still concatenated for the import-routine assertions).
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-integration-sync.ts",
);
const source =
readFileSync(REGISTRAR_PATH, "utf8") +
readFileSync(join(__dirname, "..", "reminder", "whoop-sync.ts"), "utf8");
import { INTEGRATION_BACKFILL_ADMISSION_QUEUE } from "@/lib/jobs/integration-backfill-admission";
import {
WHOOP_CYCLE_SYNC_CRON,
WHOOP_CYCLE_SYNC_QUEUE,
WHOOP_OAUTH_STATE_CLEANUP_CRON,
WHOOP_OAUTH_STATE_CLEANUP_QUEUE,
WHOOP_RECOVERY_SYNC_CRON,
WHOOP_RECOVERY_SYNC_QUEUE,
WHOOP_SLEEP_SYNC_CRON,
WHOOP_SLEEP_SYNC_QUEUE,
WHOOP_WORKOUT_SYNC_CRON,
WHOOP_WORKOUT_SYNC_QUEUE,
} from "@/lib/jobs/integration-poll-queues";
import { cronIsTheRetry } from "@/lib/jobs/reminder/registrar-shared";
import {
handleWhoopCycleSync,
handleWhoopRecoverySync,
handleWhoopSleepSync,
handleWhoopWorkoutSync,
} from "@/lib/jobs/reminder/whoop-sync";
import {
discoveryOf,
groupOf,
jobOf,
loadedHandler,
recordingBoss,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import { WHOOP_BACKFILL_QUEUE } from "@/lib/jobs/whoop-backfill-queue";
const WHOOP_QUEUE_CONSTS = [
"WHOOP_RECOVERY_SYNC_QUEUE",
"WHOOP_SLEEP_SYNC_QUEUE",
"WHOOP_WORKOUT_SYNC_QUEUE",
"WHOOP_CYCLE_SYNC_QUEUE",
"WHOOP_BACKFILL_QUEUE",
"WHOOP_OAUTH_STATE_CLEANUP_QUEUE",
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "whoop-sync.ts"),
"utf8",
);
const WHOOP_QUEUES = [
WHOOP_RECOVERY_SYNC_QUEUE,
WHOOP_SLEEP_SYNC_QUEUE,
WHOOP_WORKOUT_SYNC_QUEUE,
WHOOP_CYCLE_SYNC_QUEUE,
WHOOP_BACKFILL_QUEUE,
WHOOP_OAUTH_STATE_CLEANUP_QUEUE,
] as const;
describe("reminder-worker — WHOOP sync queues", () => {
describe("WHOOP sync queues", () => {
it("declares every WHOOP sync queue constant", () => {
expect(source).toMatch(
/WHOOP_RECOVERY_SYNC_QUEUE\s*=\s*["']whoop-recovery-sync["']/,
);
expect(source).toMatch(
/WHOOP_SLEEP_SYNC_QUEUE\s*=\s*["']whoop-sleep-sync["']/,
);
expect(source).toMatch(
/WHOOP_WORKOUT_SYNC_QUEUE\s*=\s*["']whoop-workout-sync["']/,
);
expect(source).toMatch(
/WHOOP_CYCLE_SYNC_QUEUE\s*=\s*["']whoop-cycle-sync["']/,
);
expect(WHOOP_RECOVERY_SYNC_QUEUE).toBe("whoop-recovery-sync");
expect(WHOOP_SLEEP_SYNC_QUEUE).toBe("whoop-sleep-sync");
expect(WHOOP_WORKOUT_SYNC_QUEUE).toBe("whoop-workout-sync");
expect(WHOOP_CYCLE_SYNC_QUEUE).toBe("whoop-cycle-sync");
});
it("registers EVERY WHOOP queue in allQueues (v1.4.37 dead-queue guard)", () => {
// Isolate the allQueues array body so the assertion can't be satisfied by
// an incidental mention elsewhere in the file.
const block = /const allQueues = \[([\s\S]*?)\];/.exec(source);
expect(block).not.toBeNull();
const body = block![1]!;
for (const q of WHOOP_QUEUE_CONSTS) {
expect(body).toContain(q);
it("declares EVERY WHOOP queue in the integration-sync group", () => {
for (const q of WHOOP_QUEUES) {
expect(groupOf(q).name).toBe("integration_sync");
}
});
it("schedules the four poll crons + the daily oauth-state sweep", () => {
expect(source).toMatch(
/\[WHOOP_RECOVERY_SYNC_QUEUE,\s*WHOOP_RECOVERY_SYNC_CRON\]/,
);
expect(source).toMatch(
/\[WHOOP_SLEEP_SYNC_QUEUE,\s*WHOOP_SLEEP_SYNC_CRON\]/,
);
expect(source).toMatch(
/\[WHOOP_WORKOUT_SYNC_QUEUE,\s*WHOOP_WORKOUT_SYNC_CRON\]/,
);
expect(source).toMatch(
/\[WHOOP_CYCLE_SYNC_QUEUE,\s*WHOOP_CYCLE_SYNC_CRON\]/,
);
expect(source).toMatch(
/\[\s*WHOOP_OAUTH_STATE_CLEANUP_QUEUE,\s*WHOOP_OAUTH_STATE_CLEANUP_CRON,\s*cronIsTheRetry,?\s*\]/,
expect(jobOf(WHOOP_RECOVERY_SYNC_QUEUE).cron).toBe(
WHOOP_RECOVERY_SYNC_CRON,
);
expect(jobOf(WHOOP_SLEEP_SYNC_QUEUE).cron).toBe(WHOOP_SLEEP_SYNC_CRON);
expect(jobOf(WHOOP_WORKOUT_SYNC_QUEUE).cron).toBe(WHOOP_WORKOUT_SYNC_CRON);
expect(jobOf(WHOOP_CYCLE_SYNC_QUEUE).cron).toBe(WHOOP_CYCLE_SYNC_CRON);
const sweep = jobOf(WHOOP_OAUTH_STATE_CLEANUP_QUEUE);
expect(sweep.cron).toBe(WHOOP_OAUTH_STATE_CLEANUP_CRON);
expect(sweep.scheduleOptions).toBe(cronIsTheRetry);
});
it("registers a createAndWork handler for every WHOOP sync queue", () => {
expect(source).toMatch(
/createAndWork[\s\S]{0,160}WHOOP_RECOVERY_SYNC_QUEUE[\s\S]{0,160}handleWhoopRecoverySync/,
it("binds a handler for every WHOOP sync queue", async () => {
expect(await loadedHandler(WHOOP_RECOVERY_SYNC_QUEUE)).toBe(
handleWhoopRecoverySync,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,160}WHOOP_SLEEP_SYNC_QUEUE[\s\S]{0,160}handleWhoopSleepSync/,
expect(await loadedHandler(WHOOP_SLEEP_SYNC_QUEUE)).toBe(
handleWhoopSleepSync,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,160}WHOOP_WORKOUT_SYNC_QUEUE[\s\S]{0,160}handleWhoopWorkoutSync/,
expect(await loadedHandler(WHOOP_WORKOUT_SYNC_QUEUE)).toBe(
handleWhoopWorkoutSync,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,160}WHOOP_CYCLE_SYNC_QUEUE[\s\S]{0,160}handleWhoopCycleSync/,
);
expect(source).toMatch(
/createAndWork[\s\S]{0,200}WHOOP_BACKFILL_QUEUE[\s\S]{0,260}enqueueIntegrationBackfillAdmission/,
expect(await loadedHandler(WHOOP_CYCLE_SYNC_QUEUE)).toBe(
handleWhoopCycleSync,
);
const { boss, sends } = recordingBoss();
const backfill = await jobOf(WHOOP_BACKFILL_QUEUE).load({ boss });
await backfill([
{
id: "j1",
name: WHOOP_BACKFILL_QUEUE,
data: { userId: "u1" },
expireInSeconds: 60,
heartbeatSeconds: null,
retryCount: 0,
signal: new AbortController().signal,
},
]);
expect(sends).toHaveLength(1);
expect(sends[0]!.queue).toBe(INTEGRATION_BACKFILL_ADMISSION_QUEUE);
expect(sends[0]!.data).toMatchObject({ kind: "whoop-backfill" });
});
it("wires the self-converging WHOOP backfill boot discovery", () => {
expect(source).toMatch(
/enqueueBootTimeWhoopBackfill\(\s*bootStaggerSecondsFor\("whoop-backfill"\)/,
);
expect(discoveryOf("integration_sync")).toContain("whoop-backfill");
});
it("imports the WHOOP per-resource sync routines", () => {
expect(source).toMatch(/from\s*["']@\/lib\/whoop\/sync-recovery["']/);
expect(source).toMatch(/from\s*["']@\/lib\/whoop\/sync-cycle["']/);
expect(source).toMatch(/from\s*["']@\/lib\/whoop\/sync-workout["']/);
expect(handlerSource).toMatch(
/from\s*["']@\/lib\/whoop\/sync-recovery["']/,
);
expect(handlerSource).toMatch(/from\s*["']@\/lib\/whoop\/sync-cycle["']/);
expect(handlerSource).toMatch(/from\s*["']@\/lib\/whoop\/sync-workout["']/);
});
});
@@ -1,7 +1,5 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { Job } from "pg-boss";
import { readFileSync } from "node:fs";
import { join } from "node:path";
const { syncUserEcg } = vi.hoisted(() => ({
syncUserEcg:
vi.fn<
@@ -28,7 +26,8 @@ vi.mock("@/lib/withings/sync-activity", () => ({
vi.mock("@/lib/withings/sync-sleep", () => ({
syncUserSleep: vi.fn(async () => 0),
}));
vi.mock("@/lib/jobs/reminder-satisfy", () => ({
vi.mock("@/lib/jobs/reminder-satisfy", async (importOriginal) => ({
...(await importOriginal<typeof import("@/lib/jobs/reminder-satisfy")>()),
enqueueReminderSatisfy: vi.fn(async () => undefined),
}));
vi.mock("@/lib/jobs/worker-status", () => ({
@@ -72,6 +71,13 @@ import {
handleWithingsEcgSync,
type WithingsEcgSyncPayload,
} from "../reminder/withings-sync";
import { WITHINGS_ECG_SYNC_CRON } from "../integration-poll-queues";
import {
groupOf,
jobOf,
loadedHandler,
} from "../registry/__tests__/registry-helpers";
import { WITHINGS_ECG_SYNC_QUEUE } from "../withings-ecg-queue";
function job(data: WithingsEcgSyncPayload): Job<WithingsEcgSyncPayload> {
return { data } as Job<WithingsEcgSyncPayload>;
@@ -82,10 +88,6 @@ beforeEach(() => {
ecgFindMany.mockResolvedValue([]);
isReauthRequired.mockResolvedValue(false);
});
const registrarSource = readFileSync(
join(process.cwd(), "src/lib/jobs/reminder/register-integration-sync.ts"),
"utf8",
);
describe("handleWithingsEcgSync", () => {
it("a webhook job (userId + window) does a targeted date-windowed sync", async () => {
@@ -176,17 +178,13 @@ describe("handleWithingsEcgSync", () => {
});
describe("withings ECG queue registration", () => {
it("deduplicates only queued replays and admits a rescue after work starts", () => {
const allQueues = registrarSource.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueues).not.toBeNull();
expect(allQueues![1]).toMatch(/\bWITHINGS_ECG_SYNC_QUEUE\b/);
expect(registrarSource).toMatch(
/\[WITHINGS_ECG_SYNC_QUEUE\]:\s*\{[\s\S]*?policy:\s*"short"/,
);
expect(registrarSource).toMatch(
/createAndWork<WithingsEcgSyncPayload>\(\s*boss,\s*WITHINGS_ECG_SYNC_QUEUE[\s\S]{0,120}handleWithingsEcgSync/,
it("deduplicates only queued replays and admits a rescue after work starts", async () => {
expect(groupOf(WITHINGS_ECG_SYNC_QUEUE).name).toBe("integration_sync");
const definition = jobOf(WITHINGS_ECG_SYNC_QUEUE);
expect(definition.policy?.policy).toBe("short");
expect(definition.cron).toBe(WITHINGS_ECG_SYNC_CRON);
expect(await loadedHandler(WITHINGS_ECG_SYNC_QUEUE)).toBe(
handleWithingsEcgSync,
);
});
});
+49 -56
View File
@@ -1,90 +1,83 @@
/**
* v1.4.25 — pg-boss queue registration for Withings activity +
* sleep v2 syncs.
* pg-boss queue registration for Withings activity + sleep v2 syncs.
*
* The reminder-worker imports heavy infrastructure (pg-boss, Prisma
* adapter, notification dispatchers, …) so we don't boot the whole
* worker in this test. Instead we read the source file as text and
* assert the queue constants + cron schedules + handler wiring are
* present — a fast regression guard that catches accidental deletion
* of the W17b/c plumbing without spinning up Postgres.
* Reads the worker registry instead of booting the worker (pg-boss, Prisma
* adapter, notification dispatchers, …): the queues are declared with their
* crons and bound to their handlers, a fast regression guard against
* accidental deletion of the plumbing without spinning up Postgres. The
* import-routine assertions read the handler module.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
// v1.18.1 — the queue wiring moved out of the 2143-LOC reminder-worker boot
// file into domain registrars; the Withings queues live in the integration-sync
// registrar. The dead-queue guard follows the wiring there (the handler module
// is still concatenated for the import-routine assertions below).
const REGISTRAR_PATH = join(
__dirname,
"..",
"reminder",
"register-integration-sync.ts",
import {
WITHINGS_ACTIVITY_CRON,
WITHINGS_ACTIVITY_QUEUE,
WITHINGS_ECG_SYNC_CRON,
WITHINGS_SLEEP_CRON,
WITHINGS_SLEEP_QUEUE,
} from "@/lib/jobs/integration-poll-queues";
import {
handleWithingsActivitySync,
handleWithingsSleepSync,
} from "@/lib/jobs/reminder/withings-sync";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
import { WITHINGS_ECG_SYNC_QUEUE } from "@/lib/jobs/withings-ecg-queue";
const handlerSource = readFileSync(
join(__dirname, "..", "reminder", "withings-sync.ts"),
"utf8",
);
const source =
readFileSync(REGISTRAR_PATH, "utf8") +
readFileSync(join(__dirname, "..", "reminder", "withings-sync.ts"), "utf8");
describe("reminder-worker — Withings activity + sleep v2 queues", () => {
describe("Withings activity + sleep v2 queues", () => {
it("declares the activity-sync queue at the documented cadence", () => {
expect(source).toMatch(
/WITHINGS_ACTIVITY_QUEUE\s*=\s*["']withings-activity-sync["']/,
);
expect(source).toMatch(
/WITHINGS_ACTIVITY_CRON\s*=\s*["']0 \* \* \* \*["']/,
);
expect(WITHINGS_ACTIVITY_QUEUE).toBe("withings-activity-sync");
expect(WITHINGS_ACTIVITY_CRON).toBe("0 * * * *");
});
it("declares the sleep-sync queue at the offset cadence", () => {
expect(source).toMatch(
/WITHINGS_SLEEP_QUEUE\s*=\s*["']withings-sleep-sync["']/,
);
expect(WITHINGS_SLEEP_QUEUE).toBe("withings-sleep-sync");
// :15 offset keeps it out of lockstep with the measure cron at :00.
expect(source).toMatch(/WITHINGS_SLEEP_CRON\s*=\s*["']15 \* \* \* \*["']/);
expect(WITHINGS_SLEEP_CRON).toBe("15 * * * *");
});
it("registers the activity-sync handler against pg-boss", () => {
expect(source).toMatch(/handleWithingsActivitySync\b/);
expect(source).toMatch(
/createAndWork[\s\S]{0,200}WITHINGS_ACTIVITY_QUEUE[\s\S]{0,200}handleWithingsActivitySync/,
it("binds the activity-sync handler", async () => {
expect(groupOf(WITHINGS_ACTIVITY_QUEUE).name).toBe("integration_sync");
expect(await loadedHandler(WITHINGS_ACTIVITY_QUEUE)).toBe(
handleWithingsActivitySync,
);
});
it("registers the sleep-sync handler against pg-boss", () => {
expect(source).toMatch(/handleWithingsSleepSync\b/);
expect(source).toMatch(
/createAndWork[\s\S]{0,200}WITHINGS_SLEEP_QUEUE[\s\S]{0,200}handleWithingsSleepSync/,
it("binds the sleep-sync handler", async () => {
expect(groupOf(WITHINGS_SLEEP_QUEUE).name).toBe("integration_sync");
expect(await loadedHandler(WITHINGS_SLEEP_QUEUE)).toBe(
handleWithingsSleepSync,
);
});
it("imports the new activity / sleep sync routines", () => {
expect(source).toMatch(
it("imports the activity / sleep sync routines", () => {
expect(handlerSource).toMatch(
/import\s*\{\s*syncUserActivity\s*\}\s*from\s*["']@\/lib\/withings\/sync-activity["']/,
);
expect(source).toMatch(
expect(handlerSource).toMatch(
/import\s*\{\s*syncUserSleep\s*\}\s*from\s*["']@\/lib\/withings\/sync-sleep["']/,
);
});
it("schedules both crons via boss.schedule (allQueues + schedules tables)", () => {
expect(source).toMatch(
/\[WITHINGS_ACTIVITY_QUEUE,\s*WITHINGS_ACTIVITY_CRON\]/,
);
expect(source).toMatch(/\[WITHINGS_SLEEP_QUEUE,\s*WITHINGS_SLEEP_CRON\]/);
it("schedules both crons", () => {
expect(jobOf(WITHINGS_ACTIVITY_QUEUE).cron).toBe(WITHINGS_ACTIVITY_CRON);
expect(jobOf(WITHINGS_SLEEP_QUEUE).cron).toBe(WITHINGS_SLEEP_CRON);
});
it("schedules the ECG catch-net cron at :41 so watch-only accounts pull strips", () => {
// The queue is already in allQueues + createAndWork-bound; W-1 adds the missing
// schedule entry so a payload-less cron tick walks the connection cohort.
expect(source).toMatch(
/WITHINGS_ECG_SYNC_CRON\s*=\s*["']41 \* \* \* \*["']/,
);
expect(source).toMatch(
/\[WITHINGS_ECG_SYNC_QUEUE,\s*WITHINGS_ECG_SYNC_CRON\]/,
);
// A payload-less cron tick walks the connection cohort.
expect(WITHINGS_ECG_SYNC_CRON).toBe("41 * * * *");
expect(jobOf(WITHINGS_ECG_SYNC_QUEUE).cron).toBe(WITHINGS_ECG_SYNC_CRON);
});
});
@@ -1,40 +1,41 @@
/**
* v1.4.37 dead-queue guard for the `workout-insight-generate` queue.
* Dead-queue guard for the `workout-insight-generate` queue.
*
* This queue fails open in the worst possible way. Nothing warms it, no cron
* touches it, and no read path triggers it — the ONLY thing that ever puts work
* on it is the arrival spine's dispatch. So if the queue name is missing from
* `allQueues`, pg-boss never provisions it, every `boss.send` resolves
* on it is the arrival spine's dispatch. So if the registry does not declare
* the queue, pg-boss never provisions it, every `boss.send` resolves
* successfully, every job vanishes, and the only symptom is that no workout
* ever gets a paragraph. That is indistinguishable from the feature's own
* honest empty state, which is exactly why it needs a structural guard rather
* than a behavioural one.
*
* Source-text assertions over the registrar, matching the shape of the sibling
* `*-queue.test.ts` guards.
* Reads the worker registry for the queue facts.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, expect, it } from "vitest";
const statusRegistrar = readFileSync(
join(__dirname, "..", "reminder", "register-status.ts"),
"utf8",
);
import { handleWorkoutInsightGenerate } from "@/lib/jobs/workout-insight-generate";
import {
WORKOUT_INSIGHT_GENERATE_CONCURRENCY,
WORKOUT_INSIGHT_GENERATE_QUEUE,
} from "@/lib/jobs/workout-insight-generate-shared";
import {
groupOf,
jobOf,
loadedHandler,
} from "@/lib/jobs/registry/__tests__/registry-helpers";
describe("workout-insight-generate queue wiring", () => {
it("provisions the queue in the allQueues list", () => {
const allQueues = statusRegistrar.match(
/const allQueues\s*=\s*\[([\s\S]*?)\];/,
);
expect(allQueues).not.toBeNull();
expect(allQueues![1]).toMatch(/\bWORKOUT_INSIGHT_GENERATE_QUEUE\b/);
it("provisions the queue in the status group", () => {
expect(groupOf(WORKOUT_INSIGHT_GENERATE_QUEUE).name).toBe("status");
});
it("binds a createAndWork handler that drains it", () => {
expect(statusRegistrar).toMatch(
/createAndWork[\s\S]{0,300}WORKOUT_INSIGHT_GENERATE_QUEUE[\s\S]{0,300}handleWorkoutInsightGenerate/,
it("binds the handler that drains it", async () => {
expect(await loadedHandler(WORKOUT_INSIGHT_GENERATE_QUEUE)).toBe(
handleWorkoutInsightGenerate,
);
});
@@ -42,19 +43,17 @@ describe("workout-insight-generate queue wiring", () => {
// This IS the provider path the spine deliberately does not walk. Widening
// the concurrency would let one sync's worth of workouts fan out into
// parallel completions.
expect(statusRegistrar).toMatch(
/WORKOUT_INSIGHT_GENERATE_QUEUE[\s\S]{0,300}localConcurrency:\s*WORKOUT_INSIGHT_GENERATE_CONCURRENCY/,
);
expect(jobOf(WORKOUT_INSIGHT_GENERATE_QUEUE).work).toEqual({
localConcurrency: WORKOUT_INSIGHT_GENERATE_CONCURRENCY,
});
});
it("has no cron schedule — it is dispatched by arrival only", () => {
// A cron here would be a standing invitation to regenerate, which is the
// saturation surface the design refuses.
// Tolerates the optional third schedule element, so a cron sneaking in
// with send options still trips this.
expect(statusRegistrar).not.toMatch(
/\[\s*WORKOUT_INSIGHT_GENERATE_QUEUE,\s*\w+_CRON\s*[,\]]/,
);
const job = jobOf(WORKOUT_INSIGHT_GENERATE_QUEUE);
expect(job.cron).toBeUndefined();
expect(job.scheduleOptions).toBeUndefined();
});
it("is reached from the spine only through the generator-free module", () => {
@@ -0,0 +1,8 @@
/**
* The queue facts of `achievement-unlock-sweep.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const ACHIEVEMENT_UNLOCK_SWEEP_QUEUE = "achievement-unlock-sweep";
export const ACHIEVEMENT_UNLOCK_SWEEP_CRON = "25 4 * * *";
-4
View File
@@ -31,10 +31,6 @@ import { resolveModuleMap } from "@/lib/modules/gate";
import { buildAchievementsResult } from "@/lib/gamification/achievements-result";
import { getWorkerPrisma } from "./reminder/shared";
export const ACHIEVEMENT_UNLOCK_SWEEP_QUEUE = "achievement-unlock-sweep";
export const ACHIEVEMENT_UNLOCK_SWEEP_CRON = "25 4 * * *";
export interface AchievementUnlockSweepPayload {
triggeredAt: string;
}
+36
View File
@@ -0,0 +1,36 @@
/**
* The queue facts of `apple-health-import-worker.ts`: its queue names and
* concurrency. They live apart from the worker so the registry and the upload
* routes can name the queue without loading the parser.
*/
/**
* Queue + cron for the periodic orphan-ImportJob sweep. v1.32.1
* (issue #588) — `reconcileOrphanImportJobs()` used to run only once,
* at worker boot. `restart: unless-stopped` in `docker-compose.yml`
* DOES bring a crashed/OOM-killed worker back up, so the boot-time
* pass usually catches a dead run — but only when that restart lands
* AFTER the row's heartbeat has already gone stale (30 minutes) or
* pg-boss no longer reports the backing job as live. A fast restart
* lands neither condition true yet, and because the pass runs
* exactly once per boot, a worker that then stays up and healthy
* NEVER re-evaluates that row — it is stuck in `unpacking` /
* `parsing` / `upserting` with "0 rows imported" forever and no
* failure ever surfaced to the status poll. A 15-minute cron closes
* that gap: within two ticks of the heartbeat going stale, the same
* idempotent reconcile flips the row to `failed` with an honest
* reason, so the UI's "Unpacking the archive…" spinner eventually
* turns into a visible, actionable error instead of an indefinite
* silent stall.
*/
export const IMPORT_JOB_RECONCILE_QUEUE = "apple-health-import-reconcile";
export const IMPORT_JOB_RECONCILE_CRON = "*/15 * * * *";
/** Queue name isolated from revision-1 workers during rolling deployments. */
export const APPLE_HEALTH_IMPORT_V2_QUEUE = "apple-health-import-v2";
/** Queue used by pre-revision-2 binaries; new workers only bridge its backlog. */
export const APPLE_HEALTH_IMPORT_LEGACY_QUEUE = "apple-health-import";
/** Concurrency cap per worker host. */
export const APPLE_HEALTH_IMPORT_CONCURRENCY = 1;
+5 -34
View File
@@ -55,10 +55,12 @@ import {
type StagingInUse,
} from "@/lib/import/apple-health-staging";
import { caughtAs, logCaught } from "@/lib/logging/signal";
import { HEALTH_CONNECT_IMPORT_KIND } from "@/lib/jobs/health-connect-import-worker";
import { HEALTH_CONNECT_IMPORT_QUEUE } from "@/lib/jobs/health-connect-import-queue";
import {
HEALTH_CONNECT_IMPORT_KIND,
HEALTH_CONNECT_IMPORT_QUEUE,
} from "@/lib/jobs/health-connect-import-worker";
APPLE_HEALTH_IMPORT_V2_QUEUE,
APPLE_HEALTH_IMPORT_LEGACY_QUEUE,
} from "./apple-health-import-queue";
/** `ImportJob.kind` of the rows this worker owns. */
export const APPLE_HEALTH_IMPORT_KIND = "apple_health";
@@ -76,28 +78,6 @@ function queuesForKind(kind: string): readonly string[] {
: [APPLE_HEALTH_IMPORT_V2_QUEUE, APPLE_HEALTH_IMPORT_LEGACY_QUEUE];
}
/**
* Queue + cron for the periodic orphan-ImportJob sweep. v1.32.1
* (issue #588) — `reconcileOrphanImportJobs()` used to run only once,
* at worker boot. `restart: unless-stopped` in `docker-compose.yml`
* DOES bring a crashed/OOM-killed worker back up, so the boot-time
* pass usually catches a dead run — but only when that restart lands
* AFTER the row's heartbeat has already gone stale (30 minutes) or
* pg-boss no longer reports the backing job as live. A fast restart
* lands neither condition true yet, and because the pass runs
* exactly once per boot, a worker that then stays up and healthy
* NEVER re-evaluates that row — it is stuck in `unpacking` /
* `parsing` / `upserting` with "0 rows imported" forever and no
* failure ever surfaced to the status poll. A 15-minute cron closes
* that gap: within two ticks of the heartbeat going stale, the same
* idempotent reconcile flips the row to `failed` with an honest
* reason, so the UI's "Unpacking the archive…" spinner eventually
* turns into a visible, actionable error instead of an indefinite
* silent stall.
*/
export const IMPORT_JOB_RECONCILE_QUEUE = "apple-health-import-reconcile";
export const IMPORT_JOB_RECONCILE_CRON = "*/15 * * * *";
/**
* Cron handler for the periodic orphan-ImportJob sweep. Delegates to
* the same `reconcileOrphanImportJobs()` the boot path calls — the
@@ -145,12 +125,6 @@ export async function handleImportJobReconcileTick(
);
}
/** Queue name isolated from revision-1 workers during rolling deployments. */
export const APPLE_HEALTH_IMPORT_V2_QUEUE = "apple-health-import-v2";
/** Queue used by pre-revision-2 binaries; new workers only bridge its backlog. */
export const APPLE_HEALTH_IMPORT_LEGACY_QUEUE = "apple-health-import";
/** Parser semantics carried by every newly-created ImportJob. */
export const APPLE_HEALTH_IMPORT_PARSER_REVISION = 3;
@@ -163,9 +137,6 @@ const APPLE_HEALTH_ECG_ARCHIVE_LIMITS = {
} as const;
const APPLE_HEALTH_ECG_MAX_SAMPLES = 2_000_000;
/** Concurrency cap per worker host. */
export const APPLE_HEALTH_IMPORT_CONCURRENCY = 1;
/**
* (issue #775) Memory preflight for the parse. The streaming pipeline
* keeps per-record work off the heap, so what remains resident scales
+6
View File
@@ -0,0 +1,6 @@
/**
* The queue facts of `backup-restore.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const BACKUP_RESTORE_QUEUE = "backup-restore";
+1 -2
View File
@@ -78,8 +78,7 @@ import {
type RestoreFailureCause,
} from "@/lib/export/restore-failure-cause";
import { logCaught } from "@/lib/logging/signal";
export const BACKUP_RESTORE_QUEUE = "backup-restore";
import { BACKUP_RESTORE_QUEUE } from "./backup-restore-queue";
/** Stable code of the 409 a second restore of the same account gets. */
export const BACKUP_RESTORE_ACTIVE_CODE = "backup.restore.active";
+10
View File
@@ -0,0 +1,10 @@
/**
* The queue facts of `coach-nudge.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
/** Pg-boss queue + cron — imported by the reminder worker's bootstrap. */
export const COACH_NUDGE_QUEUE = "coach-nudge";
/** 05:15 Europe/Berlin — after the 04:45–04:55 score crons settled. */
export const COACH_NUDGE_CRON = "15 5 * * *";
-5
View File
@@ -121,11 +121,6 @@ const SELF_CONTEXT_AI_SECTIONS = [
"COACH_FOCUS",
] as const satisfies readonly HealthProfileAiSection[];
/** Pg-boss queue + cron — imported by the reminder worker's bootstrap. */
export const COACH_NUDGE_QUEUE = "coach-nudge";
/** 05:15 Europe/Berlin — after the 04:45–04:55 score crons settled. */
export const COACH_NUDGE_CRON = "15 5 * * *";
/**
* Frequency cap default: one nudge per user per rolling week. v1.16.5 —
* the per-user `nudgeFrequency` pref ("weekly" | "biweekly") can widen
@@ -0,0 +1,10 @@
/**
* The queue facts of `coach-reminder-sweep.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const COACH_REMINDER_SWEEP_QUEUE = "coach-reminder-sweep";
// Daily at 05:20 Europe/Berlin — just after the 05:15 nudge tick so both
// proactive passes run on settled overnight rows.
export const COACH_REMINDER_SWEEP_CRON = "20 5 * * *";
-5
View File
@@ -71,11 +71,6 @@ import {
surfaceCoachReminders,
} from "@/lib/ai/coach/reminder-surface";
export const COACH_REMINDER_SWEEP_QUEUE = "coach-reminder-sweep";
// Daily at 05:20 Europe/Berlin — just after the 05:15 nudge tick so both
// proactive passes run on settled overnight rows.
export const COACH_REMINDER_SWEEP_CRON = "20 5 * * *";
/** Bound the plan-review fan-out per tick so one sweep can't run unbounded. */
const PLAN_REVIEW_BATCH = 200;
/** Bound the surfacing fan-out per tick for the same reason. */
@@ -0,0 +1,6 @@
/**
* The queue facts of `compaction-tombstone-purge.ts`: its name and, where it has them, its
* schedule, concurrency and send options. They live apart from the job so the
* worker registry and every sender can read them without loading it.
*/
export const COMPACTION_TOMBSTONE_PURGE_QUEUE = "compaction-tombstone-purge";
+1 -2
View File
@@ -67,8 +67,7 @@ import {
findCompactionTombstones,
} from "@/lib/measurements/folded-window";
import { prisma as defaultPrisma } from "@/lib/db";
export const COMPACTION_TOMBSTONE_PURGE_QUEUE = "compaction-tombstone-purge";
import { COMPACTION_TOMBSTONE_PURGE_QUEUE } from "./compaction-tombstone-purge-queue";
/** One key for every send, so a queued run absorbs a second boot's send. */
export const COMPACTION_TOMBSTONE_PURGE_SINGLETON_KEY =
+9
View File
@@ -0,0 +1,9 @@
/**
* The queue facts of `daily-briefing.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const DAILY_BRIEFING_QUEUE = "daily-briefing";
/** Every 15 minutes; the per-user local-hour gate selects the fallback slot. */
export const DAILY_BRIEFING_CRON = "*/15 * * * *";
-4
View File
@@ -30,10 +30,6 @@ import {
} from "@/lib/daily/daily-briefing-push";
import { DEFAULT_TIMEZONE } from "@/lib/tz/format";
export const DAILY_BRIEFING_QUEUE = "daily-briefing";
/** Every 15 minutes; the per-user local-hour gate selects the fallback slot. */
export const DAILY_BRIEFING_CRON = "*/15 * * * *";
export interface DailyBriefingTickSummary {
candidatesScanned: number;
inSlot: number;
+9
View File
@@ -0,0 +1,9 @@
/**
* The queue facts of `dawarich-sync.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const DAWARICH_SYNC_QUEUE = "dawarich-sync";
/** Daily 03:20 Europe/Berlin, after the night's environment fetch. */
export const DAWARICH_SYNC_CRON = "20 3 * * *";
+1 -5
View File
@@ -30,11 +30,7 @@ import {
import { daysBetweenDateKeys, shiftDateKey } from "@/lib/tz/format";
import { resolveUserTimezone } from "@/lib/tz/resolver";
import { DAWARICH_SYNC_MAX_DAYS } from "@/lib/validations/dawarich";
export const DAWARICH_SYNC_QUEUE = "dawarich-sync";
/** Daily 03:20 Europe/Berlin, after the night's environment fetch. */
export const DAWARICH_SYNC_CRON = "20 3 * * *";
import { DAWARICH_SYNC_QUEUE } from "./dawarich-sync-queue";
/** The nightly window: yesterday and the six days before it. */
const NIGHTLY_WINDOW_DAYS = 7;
@@ -0,0 +1,14 @@
/**
* The queue facts of `dense-intraday-hourly-rebuild.ts`: its name and, where it has them, its
* schedule, concurrency and send options. They live apart from the job so the
* worker registry and every sender can read them without loading it.
*/
export const DENSE_INTRADAY_HOURLY_REBUILD_QUEUE =
"dense-intraday-hourly-rebuild";
/**
* Serial concurrency — a rebuild walks every folded day for one account
* with a transaction per day; concurrency-1 keeps it from crowding the
* request pool, matching the retention drain's convention.
*/
export const DENSE_INTRADAY_HOURLY_REBUILD_CONCURRENCY = 1;
+1 -10
View File
@@ -34,16 +34,7 @@ import {
DENSE_INTRADAY_RETENTION_TYPES,
} from "@/lib/measurements/dense-intraday-retention";
import { runDenseIntradayHourlyRebuild } from "@/lib/measurements/dense-intraday-hourly-rebuild";
export const DENSE_INTRADAY_HOURLY_REBUILD_QUEUE =
"dense-intraday-hourly-rebuild";
/**
* Serial concurrency — a rebuild walks every folded day for one account
* with a transaction per day; concurrency-1 keeps it from crowding the
* request pool, matching the retention drain's convention.
*/
export const DENSE_INTRADAY_HOURLY_REBUILD_CONCURRENCY = 1;
import { DENSE_INTRADAY_HOURLY_REBUILD_QUEUE } from "./dense-intraday-hourly-rebuild-queue";
/**
* Boot-discovery jobs start only after this delay (seconds). The rebuild
@@ -0,0 +1,14 @@
/**
* The queue facts of `dense-intraday-retention.ts`: its name and, where it has them, its
* schedule, concurrency and send options. They live apart from the job so the
* worker registry and every sender can read them without loading it.
*/
export const DENSE_INTRADAY_RETENTION_QUEUE = "dense-intraday-retention";
/**
* Serial concurrency — the drain walks every out-of-window per-sample
* dense-tier row for a user and writes a transaction per day;
* concurrency-1 keeps it from crowding the request pool, matching the
* mean-consolidation + step-consolidation convention.
*/
export const DENSE_INTRADAY_RETENTION_CONCURRENCY = 1;
+1 -10
View File
@@ -27,16 +27,7 @@ import {
DENSE_INTRADAY_RETENTION_DAYS,
DENSE_INTRADAY_FOLD_SOURCES,
} from "@/lib/measurements/dense-intraday-retention";
export const DENSE_INTRADAY_RETENTION_QUEUE = "dense-intraday-retention";
/**
* Serial concurrency — the drain walks every out-of-window per-sample
* dense-tier row for a user and writes a transaction per day;
* concurrency-1 keeps it from crowding the request pool, matching the
* mean-consolidation + step-consolidation convention.
*/
export const DENSE_INTRADAY_RETENTION_CONCURRENCY = 1;
import { DENSE_INTRADAY_RETENTION_QUEUE } from "./dense-intraday-retention-queue";
/**
* Daily at 03:50 Europe/Berlin — inside the existing 03:xx maintenance
+9
View File
@@ -0,0 +1,9 @@
/**
* The queue facts of `document-ai-run.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const DOCUMENT_AI_RUN_QUEUE = "document-ai-run";
/** Two reads at a time: provider calls, kept off the request pool. */
export const DOCUMENT_AI_RUN_CONCURRENCY = 2;
@@ -0,0 +1,9 @@
/**
* The queue facts of `document-ai-run-reaper.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the job so the worker
* registry and every sender can read them without loading it.
*/
export const DOCUMENT_AI_RUN_REAPER_QUEUE = "document-ai-run-reaper";
/** Every five minutes: a queued run fails within twenty minutes at worst. */
export const DOCUMENT_AI_RUN_REAPER_CRON = "*/5 * * * *";
-4
View File
@@ -15,10 +15,6 @@ import { reapAiRuns } from "@/lib/documents/ai-runs/store";
import { jobDone, jobFailed, type JobOutcome } from "@/lib/jobs/job-outcome";
import { withBackgroundEvent } from "@/lib/logging/background";
export const DOCUMENT_AI_RUN_REAPER_QUEUE = "document-ai-run-reaper";
/** Every five minutes: a queued run fails within twenty minutes at worst. */
export const DOCUMENT_AI_RUN_REAPER_CRON = "*/5 * * * *";
export interface DocumentAiRunReaperPayload {
triggeredAt?: string;
}
+1 -5
View File
@@ -97,11 +97,7 @@ import {
executeSupplementLabelExtraction,
requireSupplementLabelProvider,
} from "@/lib/medications/ingredients/label-extract";
export const DOCUMENT_AI_RUN_QUEUE = "document-ai-run";
/** Two reads at a time: provider calls, kept off the request pool. */
export const DOCUMENT_AI_RUN_CONCURRENCY = 2;
import { DOCUMENT_AI_RUN_QUEUE } from "./document-ai-run-queue";
/**
* The job's expiry: one hour. Three model calls at the longest response
@@ -0,0 +1,9 @@
/**
* The queue facts of `document-content-index-backfill.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const CONTENT_INDEX_BACKFILL_QUEUE = "document-content-index-backfill";
/** Serial concurrency — provider calls, kept off the request pool. */
export const CONTENT_INDEX_BACKFILL_CONCURRENCY = 1;
@@ -55,11 +55,7 @@ import { transcribeDocument } from "@/lib/documents/describe";
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import { resolveDocumentVisionProvider } from "@/lib/documents/provider-order";
import { annotate } from "@/lib/logging/context";
export const CONTENT_INDEX_BACKFILL_QUEUE = "document-content-index-backfill";
/** Serial concurrency — provider calls, kept off the request pool. */
export const CONTENT_INDEX_BACKFILL_CONCURRENCY = 1;
import { CONTENT_INDEX_BACKFILL_QUEUE } from "./document-content-index-backfill-queue";
/** Discovery page size (id-only; blobs are loaded one at a time). */
const PAGE_SIZE = 50;
+9
View File
@@ -0,0 +1,9 @@
/**
* The queue facts of `document-index.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const DOCUMENT_INDEX_QUEUE = "document-index";
/** Serial concurrency — provider calls + PDF parse, kept off the request pool. */
export const DOCUMENT_INDEX_CONCURRENCY = 1;
+1 -5
View File
@@ -17,11 +17,7 @@ import { indexDocumentContent } from "@/lib/documents/index-document";
import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import { annotate } from "@/lib/logging/context";
import { caughtAs, logCaught } from "@/lib/logging/signal";
export const DOCUMENT_INDEX_QUEUE = "document-index";
/** Serial concurrency — provider calls + PDF parse, kept off the request pool. */
export const DOCUMENT_INDEX_CONCURRENCY = 1;
import { DOCUMENT_INDEX_QUEUE } from "./document-index-queue";
export interface DocumentIndexPayload {
userId: string;
@@ -0,0 +1,9 @@
/**
* The queue facts of `document-summary-catchup.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const DOCUMENT_SUMMARY_CATCHUP_QUEUE = "document-summary-catchup";
/** Serial concurrency — cheap discovery + fan-out enqueues. */
export const DOCUMENT_SUMMARY_CATCHUP_CONCURRENCY = 1;
+1 -5
View File
@@ -40,11 +40,7 @@ import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import { enqueueDocumentSummary } from "@/lib/jobs/document-summary";
import { annotate } from "@/lib/logging/context";
import { logCaught } from "@/lib/logging/signal";
export const DOCUMENT_SUMMARY_CATCHUP_QUEUE = "document-summary-catchup";
/** Serial concurrency — cheap discovery + fan-out enqueues. */
export const DOCUMENT_SUMMARY_CATCHUP_CONCURRENCY = 1;
import { DOCUMENT_SUMMARY_CATCHUP_QUEUE } from "./document-summary-catchup-queue";
/** Discovery page size (id-only). */
const PAGE_SIZE = 100;
+9
View File
@@ -0,0 +1,9 @@
/**
* The queue facts of `document-summary.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the handler so the worker
* registry and every sender can read them without loading the job.
*/
export const DOCUMENT_SUMMARY_QUEUE = "document-summary";
/** Serial concurrency — provider calls, kept off the request pool. */
export const DOCUMENT_SUMMARY_CONCURRENCY = 1;
@@ -0,0 +1,9 @@
/**
* The queue facts of `document-summary-reaper.ts`: its name and, where it has them, its
* schedule and concurrency. They live apart from the job so the worker
* registry and every sender can read them without loading it.
*/
export const DOCUMENT_SUMMARY_REAPER_QUEUE = "document-summary-reaper";
/** Hourly at :50 — off the maintenance herd's minute marks. */
export const DOCUMENT_SUMMARY_REAPER_CRON = "50 * * * *";
-4
View File
@@ -30,10 +30,6 @@ import { getWorkerPrisma } from "@/lib/jobs/reminder/shared";
import { SUMMARY_PENDING_TTL_MS } from "@/lib/documents/store";
import type { PrismaClient } from "@/generated/prisma/client";
export const DOCUMENT_SUMMARY_REAPER_QUEUE = "document-summary-reaper";
/** Hourly at :50 — off the maintenance herd's minute marks. */
export const DOCUMENT_SUMMARY_REAPER_CRON = "50 * * * *";
export interface DocumentSummaryReaperPayload {
triggeredAt?: string;
}
+1 -5
View File
@@ -49,8 +49,7 @@ import { getGlobalBoss } from "@/lib/jobs/boss-instance";
import { annotate } from "@/lib/logging/context";
import type { DocumentSummaryState } from "@/generated/prisma/client";
import { logCaught } from "@/lib/logging/signal";
export const DOCUMENT_SUMMARY_QUEUE = "document-summary";
import { DOCUMENT_SUMMARY_QUEUE } from "./document-summary-queue";
/**
* Record what became of this document's summary. Never touches a READY row —
@@ -73,9 +72,6 @@ async function markSummaryState(
});
}
/** Serial concurrency — provider calls, kept off the request pool. */
export const DOCUMENT_SUMMARY_CONCURRENCY = 1;
export interface DocumentSummaryPayload {
userId: string;
documentId: string;
Loaded 100 of 193 files, more files were not shown because too many files have changed in this diff. Show more