# Deployment This page gives the one supported way to run the deadline example: the Node server as one process on a host you control. There is no second topology in scope, so every step below is the whole procedure. The limits below describe what `src/server.ts` does today; they are not recommendations. ```text docs/build/examples/deadline-app ``` ## Prerequisites - Node.js: minimum 20 (`engines: >=20`), recommended 24 Active LTS for a new deployment (`node --version` tells you). - The published packages the example pins: ```text @arxo/law 0.3.3 @arxo/canon-bgb-fristen 0.1.5 ``` - The example sources with their lockfile, so `npm ci` reproduces the tested tree. ## Install and start ```text npm ci npm start ``` `npm ci` installs the pinned tree, including the canon package with the engine files it ships. `npm start` launches the `node:http` server from `src/server.ts`. ## Access mode The server binds loopback only on port 8787 unless told otherwise: ```text bind 127.0.0.1 (HOST overrides) port 8787 (PORT overrides) ``` `HOST=0.0.0.0` exposes the example on the network — set it deliberately, never by default. The example has no authentication, no TLS, and no rate limiting: beyond loopback it needs a front proxy, and that proxy is outside this page. Captures land in `captures/` under the app directory; `DEADLINE_APP_CAPTURES` points them elsewhere. ## Limits What the server enforces, and what it does not: | Limit | Behaviour | Consequence | |---|---|---| | Request body | Bodies over 64 KiB are refused with `413` | Oversized input never reaches validation or the engine | | Receiving a request | Node's default timeouts apply; the server sets none | Slow clients are cut off while sending, never while the engine computes | | Computation time | No application deadline, no cancellation | A slow computation runs to completion, however long it takes | | Simultaneous computations | No cap; every POST evaluates independently | One process does not mean one request at a time | | Request log | One listen line at startup, nothing per request | The audit trail is the captures, not stdout | The details behind each row: - **Body size: 64 KiB.** `readBody` refuses anything larger (`MAX_BODY` in `src/server.ts`) with `413` and a `body too large` error: the server declines content over its own limit, which is exactly what `413 Content Too Large` means. The upload is paused first, the 413 is sent, and the socket is destroyed only after the response flushes — the client receives the status instead of a bare connection reset. - **Request receipt: Node defaults, not overridden.** The server is created with no timeout options, so `node:http` applies its own: `requestTimeout` 300000 ms to receive a full request, `headersTimeout` 60000 ms for the headers, `keepAliveTimeout` 5000 ms on idle keep-alive sockets, no socket inactivity timeout (`timeout` 0), no per-socket request cap. These bound receiving, not computing. - **Computation: no application deadline, no cancellation.** Once a request is received, the engine call runs to completion: a slow computation holds the event loop's attention until the engine answers, however long that takes. - **No cap on simultaneous computations.** Every POST evaluates independently through the shared singleton model handle. - **No request log.** The example prints one listen line at startup and nothing per request; the security page says what may and may not go into logs if you add them. On memory sizing this page makes no claim: sizing is not established. Steady state is the model plus one working set per concurrent request, and only a measured control load plus margin can size it. The form route cannot stand in for that measurement (see readiness below). ## Readiness Two different questions, two different probes: - **Is the HTTP process up?** `GET /` answers 200 with the form. That proves the process listens and the static file reads — nothing about the engine. - **Can it compute?** POST the worked case to `/api/deadline/evaluate` (event `2026-03-06`, 14 days, legal time `2026-09-17`) and expect `COMPUTED` with `2026-03-20`. That proves the model opened and the full path — validate, facts, query, engine, read, view, capture — runs. A supervisor healthcheck should use the second probe, or the first plus the second: the form page passing while evaluation fails is a real failure mode (missing canon, broken pins), and the form alone will not catch it. That probe is not side-effect free: every evaluate call stores a new capture file under a random id, with no dedupe. Run the supervised instance with `DEADLINE_APP_CAPTURES` pointed at a scratch directory and rotate that directory, so probe traffic never mixes with kept captures and never fills the disk silently. ## Shutdown and storage - **Stopping drops in-flight requests.** The server installs no signal handler and drains nothing: SIGTERM ends the process, and whatever request was mid-computation never answers. Stop it when no computation matters, or accept the dropped request. - **A capture write can be interrupted.** `storeCapture` writes the file synchronously under its final name — there is no write-to-temp-and-rename. A crash mid-write leaves a partial file; replaying it later fails (truncated JSON reads as a 500 on the replay call). Treat that as a corrupt file and recompute the case; never hand-edit it back into shape. - **Restart is otherwise stateless.** No sessions, no cache, no warmup beyond opening the pinned model at startup. Stop the process, start it with the same command and the same `HOST`/`PORT`, and run the control query to confirm. ## Diagnostics | Symptom | Likely cause | What to do | |---|---|---| | `npm start` fails naming the Node version | Node older than 20 | install a supported LTS (24 recommended; 20 is the floor) and retry | | startup fails opening the model | canon package missing or version drift | run `npm ci` again; confirm `@arxo/canon-bgb-fristen 0.1.5` is installed | | `listen EADDRINUSE` on start | the port is busy | set `PORT` to a free port and retry | | `GET /` answers but the control query fails | run path broken past the static file | confirm the model opened with `offline: true` and `via` is `local`; see the offline page | | `413 body too large` on evaluate | form over 64 KiB | shrink the body; the example takes four short fields | | replay disagrees with a stored capture | pins moved since the capture | do not edit the capture; replay under the stored pins or recompute as a new file | | replay of a capture answers 500 | partial file from an interrupted write | recompute the case as a new capture | When a failure names a version, trust the version: the pins (`@arxo/law 0.3.3`, `@arxo/canon-bgb-fristen 0.1.5`, model `de.bgb.fristen@0.1.0`) are the supported combination, and anything else is untested until the versioning checklist says otherwise. ## What is not in scope One host, one process, one port. Anything beyond that — extra instances, shared capture stores, front proxies, graceful shutdown, log pipelines — sits outside this page and adds its own failure modes the example does not handle. If you outgrow one process, keep this page as the unit that each copy runs. ## Next - [Versioning: pins, upgrades, and old captures](/build/application/versioning/)