PRIVATE CALCULATION SERVICE — DEPLOYMENT CANDIDATE, 2 OCTOBER 2026

Purpose: use the already implemented native calculation profile without a
production dependency on astro.com's test/debugging website. No deployment,
licence purchase or customer activation is included in this change.

Build context: services/astro
  docker build -t moon-node-astro .
The Dockerfile copies only package metadata, pinned dependencies and source.
No application credentials, customer files or local job store enter the image.
Docker is not installed on the current workstation: the image build itself
must be verified on the selected Linux host before activation. The native
engine-to-contract-to-evidence-to-PDF chain and HTTP queue are tested locally.

Host requirements: Linux container, native Node addon support, persistent disk,
TLS at the ingress, ONE container/replica for this volume, no sleep-on-idle.
Start with a 5GB volume at /data and one worker. Two separate child processes
can be selected after measuring on a host with two available CPU cores; native
global state is isolated per process. ASTRO_ENGINE_CONCURRENCY accepts 1..4.
Keep automatic redeploy overlap disabled for this volume. A restart may repeat
an interrupted calculation, but never loses an acknowledged queued request.
A completed result is reused for 24h. The application stores accepted results
in its own private database before proceeding, so they do not depend on this
cache after that. No customer name, email or place name is sent to the engine.

Required runtime secrets/settings, server-side only:
  ASTRO_ENGINE_LICENCE_ACK=1 (only AFTER the licence decision)
  ASTRO_ENGINE_API_KEY=<random secret, minimum 32 characters>
  ASTRO_ENGINE_DATA_DIR=/data/astro
  NODE_ENV=production
  ASTRO_ENGINE_HOST=0.0.0.0
  PORT=<host port; default 8787>
  ASTRO_ENGINE_CONCURRENCY=1
  ASTRO_ENGINE_MAX_PENDING=200
Never set ASTRO_ENGINE_WORKER_PATH in production (the override is rejected).
The service refuses production start without an absolute persistent directory.
The entrypoint gives the dedicated directory to user node; service runs as node.

Application settings, server-side only:
  ASTRO_CALCULATION_PROVIDER=native_reference
  ASTRO_REFERENCE_URL=https://<private-service-host>/v1/calculate
  ASTRO_REFERENCE_KEY=<same secret>
  ASTRO_REFERENCE_LICENCE_ACK=1
The default remains astrocom_swetest for the existing TEST flow. An unknown
provider is rejected. Merely saving source does not change the active provider.

HTTP: POST /v1/calculate with x-engine-key and x-engine-async: 1.
202 = fsynced acceptance or still waiting; poll the identical canonical input.
200 = cached result; 409 = conflicting input for the same order/version or
missing async header; 422 = invalid input or cached calculation error;
503 = full queue/storage failure; retries cannot create duplicate queued work.
A queued wait does not consume the application's five failure attempts. It is
polled once per minute with a persisted maximum wait of 24 hours, then held
for review. The engine kills one calculation after 10 minutes. New requests
are capped at 200 pending/running and 1000 retained records (max 4MiB/result).
Files are private (0600) in a 0700 directory. Sweep removes completed records
after 24h on traffic/restart. Disk backups must also follow retention policy.

Authenticated GET /healthz exposes only counts and storage readiness. The
Docker healthcheck supplies the key internally. Do not configure an unauthenticated
host HTTP healthcheck against this endpoint; use the container healthcheck/TCP.
Alert on unhealthy state, queue age and disk pressure using host monitoring.
A corrupted store fails closed; restore the volume or recover individual
synthetic/known requests after investigation. Never start two writers on the
same volume. Writer lock includes Linux process start time to detect PID reuse.

Acceptance on selected host: build the image; verify native engine version;
submit one synthetic request; restart while queued; obtain and validate its
result; ensure unauthenticated requests fail; confirm no request bodies/secrets
in ingress logs; measure a realistic burst. Do not claim 200/day capacity from
local synthetic queue tests or a single measured calculation. At 200/day the
average is one order per 7.2 minutes; simultaneous bursts govern latency.
