> ## Documentation Index
> Fetch the complete documentation index at: https://cona.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Temporal Workers

> Inside @cona/temporal-workers — bootstrap order, worker factories, health checks, and graceful shutdown

# Temporal Workers

`@cona/temporal-workers` — the Node process that polls Temporal task queues and executes
activities. Runs on Fly.io in `fra`.

For queue routing and the workflows themselves see
[Temporal Orchestration](/architecture/temporal-orchestration); for VM sizing see
[Deployment Topology](/architecture/deployment-topology).

## Bootstrap order

Startup order matters here: environment validation happens **before** any connection is
opened, and worker-group selection happens before module load so Prisma pools size
correctly.

```mermaid theme={null}
flowchart TB
    Entry{"entrypoint"}
    Entry -->|"dist/worker.js<br/>production, all groups"| Env
    Entry -->|"dist/worker-group.js &lt;group&gt;<br/>staging, one group"| Configure["configureWorkerGroupEnvironment<br/>sets WORKER_GROUP before import"]
    Configure --> Env

    Env["validateEnvironment<br/>worker.ts:47"]
    Env -->|"fail fast, no connections open"| Exit["exit"]
    Env --> Telemetry["OpenTelemetry + logger init"]
    Telemetry --> Health["startHealthCheckServer<br/>:8080 · health.ts:49"]
    Health --> Factories["getWorkerFactories<br/>workers/index.ts:61"]
    Factories --> Create["createWorker per task queue<br/>bundle workflows, register activities"]
    Create --> Accounting["ensureAccountingQueueRunning<br/>worker.ts:62"]
    Accounting --> Run["Promise.allSettled(worker.run())"]

    classDef async fill:#e3f7ea,stroke:#2f9e5c,color:#111827
    classDef app fill:#dbe2fb,stroke:#3B56C5,color:#111827
    class Env,Telemetry,Health,Factories,Create,Accounting,Run,Configure async
    class Entry,Exit app
```

`worker-group.ts` is deliberately eight lines with a comment explaining why: worker
dependencies initialise Prisma pools at module load, so `WORKER_GROUP` must be set before
`worker.js` is imported, not inside `run()`.

## Worker factories

`workers/index.ts:39-59` declares 19 production factories, each tagged with a group.
`getWorkerFactories()` filters by `WORKER_GROUP` (`all` starts everything) and appends
the test-document factory only when `NODE_ENV !== "production"`.

```mermaid theme={null}
flowchart LR
    subgraph imports["imports — 19 workers"]
        I1["shopify ×2 · amazon ×2<br/>mirakl ×6"]
        I2["paypal · docmorris · stripe<br/>otto · bank-account · fx<br/>tiktok · orderchamp · xentral"]
    end
    subgraph misc["misc — 5 workers"]
        M1["gdpr · data-retention<br/>organization-deletion<br/>billing · datev-export"]
    end
    subgraph acc["accounting — 2"]
        A1["accounting-queue<br/>recreate-accounting-impact-batch"]
    end
    subgraph rec["reconciliation — 2"]
        R1["reconciliation-global<br/>reconciliation-org"]
    end

    classDef boundary fill:#f8fafc,stroke:#94a3b8,color:#334155

    classDef async fill:#e3f7ea,stroke:#2f9e5c,color:#111827
    class I1,I2,M1,A1,R1 async
    class imports,misc,acc,rec boundary
```

Three factories produce more than one worker: `shopify` (parent + child), `amazon`
(settlements + VAT report), `accounting` and `reconciliation` (two lanes each), and
`mirakl` (one template × 6 marketplaces).

## Health and shutdown

The health server is an **internal Fly Machine check** on `:8080/health` — workers expose
no public port and sit behind no proxy.

Shutdown is ordered deliberately (`worker.ts:261-293`):

1. On `SIGTERM`, **close the health server first** so Fly stops routing before workers
   drain.
2. Call `worker.run()` shutdown; promises resolve when each worker reaches `STOPPED`.
3. `Promise.allSettled`, **not** `Promise.all` — the comment at `worker.ts:277` explains
   that `all` short-circuits on first rejection and would lose the per-worker failure log.
4. Flush telemetry. OTLP export is best-effort; the telemetry package absorbs exporter
   failures so observability can never block shutdown (`worker.ts:377-378`).

Fly caps `kill_timeout` at 300s. The worker force deadline is 4m30s, leaving 30s for
connection, logger, and telemetry cleanup.

## Source layout

| File                                                               | Role                                                  |
| ------------------------------------------------------------------ | ----------------------------------------------------- |
| `worker.ts`                                                        | main bootstrap and shutdown                           |
| `worker-group.ts`                                                  | group entrypoint — sets env, then imports `worker.js` |
| `worker-group-env.ts`                                              | validates and applies `WORKER_GROUP`                  |
| `workers/index.ts`                                                 | factory registry and group filter                     |
| `workers/create-worker.ts`                                         | shared `createWorker`, workflow bundling and cache    |
| `workers/<domain>.ts`                                              | 20 files, one per domain                              |
| `health.ts`                                                        | readiness state + HTTP check server                   |
| `telemetry.ts`, `temporal-opentelemetry.ts`, `temporal-metrics.ts` | OTel wiring                                           |
| `logger.ts`, `redis-logger-adapter.ts`, `temporal-log-metadata.ts` | logging                                               |
| `build-workflow-bundles.ts`                                        | pre-bundles workflow code                             |
| `worker-runtime-context.ts`                                        | per-worker context                                    |

## Dependencies

| Package                    | Imports |
| -------------------------- | ------: |
| `@cona/temporal-config`    |      30 |
| `@cona/temporal-workflows` |      20 |
| `@cona/observability`      |       4 |
| `@cona/core`               |       2 |
| `@cona/opentelemetry`      |       2 |
| `@cona/utils`              |       1 |

Only 2 direct `@cona/core` imports — business logic is reached through activities in
`@cona/temporal-workflows`, which imports core 292 times. The layering holds.

## Notes

**The accounting queue gets special treatment.** `ensureAccountingQueueRunning`
(`worker.ts:62`) runs after worker creation to guarantee the long-lived
`syncAccountingQueueWorkflow` is active. See
[Accounting Engine](/architecture/accounting-engine).

**Workflow bundles are cached.** `create-worker.ts` exposes
`getWorkflowBundleCacheStats()` and `buildWorkflowBundle()`; bundling all workflows per
worker would otherwise dominate startup.

**No Sentry.** Workers report to Axiom and OpenTelemetry only.
