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.
docs/build/examples/deadline-appPrerequisites
Section titled “Prerequisites”- Node.js: minimum 20 (
engines: >=20), recommended 24 Active LTS for a new deployment (node --versiontells you). - The published packages the example pins:
@arxo/law 0.3.3@arxo/canon-bgb-fristen 0.1.5- The example sources with their lockfile, so
npm cireproduces the tested tree.
Install and start
Section titled “Install and start”npm cinpm startnpm 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
Section titled “Access mode”The server binds loopback only on port 8787 unless told otherwise:
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
Section titled “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.
readBodyrefuses anything larger (MAX_BODYinsrc/server.ts) with413and abody too largeerror: the server declines content over its own limit, which is exactly what413 Content Too Largemeans. 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:httpapplies its own:requestTimeout300000 ms to receive a full request,headersTimeout60000 ms for the headers,keepAliveTimeout5000 ms on idle keep-alive sockets, no socket inactivity timeout (timeout0), 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
Section titled “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(event2026-03-06, 14 days, legal time2026-09-17) and expectCOMPUTEDwith2026-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
Section titled “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.
storeCapturewrites 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
Section titled “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
Section titled “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.
Documentation for Arxo. Writings — blog.arxo.io.
Anonymous visit counts on stats.arxo.io, no cookies.