# The Plantoo worker

One Node process beside the queue worker, doing the two things PHP should not (FP-T26, C2a).

**Why it exists.** Filling an official PDF means a document toolchain, and running a spreadsheet's
formulas means a formula engine. Both are Node, both are slow, and neither belongs inside a web
request. So they happen here.

**One service, two job types.** Rendering a document is built; running a workbook is D2's and
arrives as a new handler in `jobs/`, not as a second process. A supervised process costs the same
to run, deploy and watch whether it does one job or two — which is the whole reason the operations
cost is paid once.

**The contract is the `render_job` table**, not Laravel's queue. A serialised PHP job is not
readable from Node, so the two runtimes meet in plain columns: `kind` says which handler, `payload`
says what to do, `result` says what was done. Rows are claimed under a row lock, so two workers —
or a worker racing a restart — cannot render the same document twice.

## Running it

    node worker/index.js

It reads the same `.env` as the application: `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`,
`DB_PASSWORD`, and `FILESYSTEM_ROOT` (defaults to `storage/app/public`). Nothing else.

## Kept alive

Only under `WORKER_MODE=supervised`. With `on_demand` — what the testing host runs — there is no
process here at all: a queued job starts this with `--once` when a step asks for a document, and
collects the results afterwards. See `DEPLOY.md`.

Supervised, it is a systemd unit beside the queue worker's, in the same shape:

    [Unit]
    Description=Plantoo Testing render worker
    After=network.target mysql.service

    [Service]
    User=www-data
    Group=www-data
    WorkingDirectory=/var/www/html/plantoo_tech_testing
    ExecStart=/usr/bin/node /var/www/html/plantoo_tech_testing/worker/index.js
    Restart=always
    RestartSec=5
    TimeoutStopSec=30

    [Install]
    WantedBy=multi-user.target

**`TimeoutStopSec` matters.** The loop finishes the job in hand before exiting, so a deploy that
kills it mid-render leaves a row Running and a half-written file. Thirty seconds is longer than any
render; systemd's default of 90 would do, but saying it is what makes it a decision.

**A row stuck in `Running` is how a crashed worker is found.** Nothing reclaims one automatically,
deliberately: a document rendered twice is worse than one rendered late, and an official form filed
twice is worse again.

## Logs

One line per job, to stdout, for journald or the queue worker's log to capture — `claimed`, `done` with the hash, or
`failed` with the reason. A failed job is NOT retried: a render that failed will fail the same way
next time, because the template is what it is. It waits for a person.
