Architecture¶
This page describes how a request becomes an engine invocation, and why the layers are divided the way they are.
The governing rule¶
Every feature is decided in this order:
- Use an existing library routine. Search the installed engines first and prefer the routine the library authors optimized.
- Only if no library provides it, write optimized C or C++ using GMP and FLINT, exposed through a narrow subprocess boundary.
- Python and JavaScript are interface, API and orchestration only. Neither computes a mathematical result, ever.
This is enforced by the test suite, not merely documented. test_native_computation_policy.py
asserts that no third-party mathematics library is imported, that every mathematical
module dispatches to an engine and names the routine that computes its results, that
transport and formatting layers decide nothing mathematical, that JavaScript never defines
a primality test, and that every Math.* call in the browser is canvas geometry.
The path of a request¶
browser form
↓ fetch with the per-launch session token
FastAPI route validates input, bounds every parameter
↓
Python boundary module builds the engine call, launches it, parses tagged output
↓
native engine PARI/GP, FLINT/Arb, YAFU, CADO-NFS, or a compiled helper
↓
tagged output TAG:value lines ending in a completion marker
↓
Python boundary module validates completeness, raises on a missing marker
↓
outputs.py writes a plain-text report, returns output/<filename>
↓
browser renders the returned values; computes nothing
The tagged-output protocol¶
Engines and Numerisect communicate through lines of the form TAG:value or
TAG:a|b|c, ending with a completion marker such as DONE: that carries a count.
Two rules make this reliable:
- The completion marker is mandatory. Its absence is an error, never an empty successful result. A run killed by a timeout cannot look like a search that found nothing.
- Counts are checked. The number of records parsed must equal the count the engine reported, or the result is rejected as inconsistent.
Searches that stop at a bound set an explicit truncation flag, so a partial list is never mistaken for a complete one.
Why a subprocess boundary¶
Engines run as separate processes in their own process group, invoked with argument
arrays rather than shell strings. This buys several things at once: a hostile input
cannot reach a shell; a runaway engine can be cancelled by signalling its group; per-job
CPU, memory and wall-clock limits can be applied before exec; and a crash in a native
library cannot take the application with it.
Long-running work goes through the job system, which persists state in SQLite, streams engine logs, supports cancellation and pause, and resumes CADO-NFS from parameter snapshots.
What Python is allowed to do¶
Validation, process launch, tagged-output parsing, persistence, HTTP, and formatting.
Integer expressions are parsed by an audited AST walker that accepts only integer
literals and the operators + - * // % **, with no names, calls or floats, and with
node-count, exponent and bit-length limits.
Bookkeeping such as grouping job runtimes into digit buckets is not a mathematical result and is permitted. Deciding divisibility, primality or a gcd is not.
What JavaScript is allowed to do¶
Render. It may map engine-supplied integers and flags to canvas coordinates and colours, animate the playback of a step trace an engine produced, and format strings. Spiral coordinates are layout; which integers are prime is not, and that always arrives from an engine.
Persistence and provenance¶
Every operation that produces a result saves a plain-text report and returns its exact
output/<filename> path. Completed factorizations additionally produce a JSON manifest
recording the commands run, the pinned engine revisions, the SHA-256 of each executable
used, the parameters, and the resulting factors, so a result can be reproduced and
audited later.