Skip to content
docs
Arxo ↗

Deployment

For LLMs9 sections

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.

Output
docs/build/examples/deadline-app
  • Node.js: minimum 20 (engines: >=20), recommended 24 Active LTS for a new deployment (node --version tells you).
  • The published packages the example pins:
Output
@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.
Output
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.

The server binds loopback only on port 8787 unless told otherwise:

Output
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.

What the server enforces, and what it does not:

LimitBehaviourConsequence
Request bodyBodies over 64 KiB are refused with 413Oversized input never reaches validation or the engine
Receiving a requestNode’s default timeouts apply; the server sets noneSlow clients are cut off while sending, never while the engine computes
Computation timeNo application deadline, no cancellationA slow computation runs to completion, however long it takes
Simultaneous computationsNo cap; every POST evaluates independentlyOne process does not mean one request at a time
Request logOne listen line at startup, nothing per requestThe 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).

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.

  • 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.
SymptomLikely causeWhat to do
npm start fails naming the Node versionNode older than 20install a supported LTS (24 recommended; 20 is the floor) and retry
startup fails opening the modelcanon package missing or version driftrun npm ci again; confirm @arxo/canon-bgb-fristen 0.1.5 is installed
listen EADDRINUSE on startthe port is busyset PORT to a free port and retry
GET / answers but the control query failsrun path broken past the static fileconfirm the model opened with offline: true and via is local; see the offline page
413 body too large on evaluateform over 64 KiBshrink the body; the example takes four short fields
replay disagrees with a stored capturepins moved since the capturedo not edit the capture; replay under the stored pins or recompute as a new file
replay of a capture answers 500partial file from an interrupted writerecompute 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.

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.