Status: draft and normative for generated API backends targeting PolyAPI
language 0.2 or 0.3 and polyapi.ir/v2 or polyapi.ir/v3. This profile defines
target identities, runtime packaging, private transport, lifecycle, and
cross-language conformance. It does not make an unimplemented generator
conforming.
Compiler target names are stable, lowercase identifiers. Selecting a target changes only the generated artifact and runtime adapter; it must not change the meaning of the canonical IR.
| Target | Generated artifact | Runtime profile | Persistence profile | Status |
|---|---|---|---|---|
go |
Go package linked into polyhost |
net/http handler served on a private UDS |
GORM/pgx | implemented |
python |
embedded Python program | CPython child serving HTTP/1.1 on a private UDS | SQLAlchemy/psycopg 3 | implemented |
rust |
embedded native executable for one OS/architecture | Tokio + Hyper HTTP/1.1 on a private UDS | SeaORM/SQLx Postgres | implemented |
java |
embedded self-contained executable JAR | Java 21+ child using Netty epoll HTTP/1.1 on a private UDS | Hibernate/JDBC | implemented |
postgres |
SQL migration | migration job, not an API backend | authoritative schema | implemented |
bundle |
Go embed.FS package and manifest |
consumed by polyhost |
contains the migration | implemented |
rust and java are registered compiler targets. Unsupported IR/profile
features fail before emission; neither generator emits a placeholder backend.
The distributable host is a single Go executable containing its generated
runtime assets through go:embed. The Go backend is linked into that executable.
Python source, Rust executables, Java JARs, the canonical IR, original .poly
source, backend manifest, and Postgres migration are embedded assets when their
targets are selected.
Every manifest backend entry must identify:
- its stable target name and artifact kind;
- its embedded asset path, except for the linked Go handler;
- the SHA-256 digest and byte length of the artifact;
- required runtime and minimum runtime version, if any;
- OS and architecture for native artifacts;
- the exact PolyAPI language version, IR version, and compiler version;
- the supported sanitizer intrinsics, sink capabilities, and store providers.
A Rust bundle contains one entry per supported OS/architecture and the host selects an exact match. It must never execute an artifact built for another platform. A Java JAR is platform-neutral only if all of its dependencies are; any platform-specific transport dependency must be declared and matched like a native Rust artifact.
At startup, polyhost verifies embedded asset digests before materialization.
It creates a new private directory with mode 0700, writes scripts and JARs
with mode 0600, writes native executables with mode 0700, and refuses
symlinks or paths escaping that directory. Runtime assets and socket files are
removed during normal shutdown. Failure to verify, materialize, or launch one
configured backend fails host startup; it does not silently reduce the selected
backend set.
Python, Rust, and Java backends are host-managed child processes. The host passes configuration through a typed process configuration, not generated source or command-line interpolation. The child receives at least:
| Setting | Meaning |
|---|---|
POLYAPI_SOCKET |
Absolute path of the backend's private Unix-domain socket. |
DATABASE_URL |
Credential-bearing Postgres connection URL. |
POLYAPI_DATABASE_CONNECT_TIMEOUT |
Database startup connection deadline. |
POLYAPI_BACKEND_READ_HEADER_TIMEOUT |
HTTP header-read deadline. |
POLYAPI_BACKEND_READ_TIMEOUT |
Deadline for reading the complete backend request, including its body. |
The socket path and runtime artifact path are chosen by the host and are not operator-provided backend addresses. Credentials must not appear in the child command line, manifest, logs, health response, or introspection endpoints.
A child must connect to Postgres and bind its UDS before becoming ready. The
host polls GET /healthz until the configured start timeout. A successful
health response is 200 with a bounded JSON body and does not expose database
or build secrets. On shutdown the host signals each child, waits for the shared
shutdown deadline, then forcefully terminates children that have not exited.
The host remains responsible for removing the socket and private directory.
Every API backend speaks HTTP/1.1 over a filesystem Unix-domain stream socket. Generated backends must not bind an internal TCP listener. The public Go proxy is the only network listener and routes requests to a healthy backend using a per-backend UDS transport.
The socket directory is mode 0700; each socket is accessible only to the host
user. A backend may remove a stale socket only when the path is inside the
host-created private directory and is a socket owned by the same user. Socket
paths must respect the platform's UDS length limit.
All targets must agree on request limits, header behavior, JSON media type, status codes, stable error envelopes, cancellation, and graceful connection close behavior. Target framework defaults are not language semantics. The proxy's configured header removal and static-header rules are applied outside the generated backend and therefore remain identical for every target. Compiler-owned headers such as authorization, content type, content length, source digest, variant selection, cache control, and content-type protection cannot be removed or replaced by operator header rules.
When an IR profile uses the client network peer as a security input, the public router owns that value. Forwarded addresses are considered only when enabled and the immediate peer belongs to an explicitly configured trusted CIDR. The router overwrites any caller-supplied internal source header with a keyed digest before UDS proxying; raw address text cannot reach a generated backend.
The following are required mappings, not suggestions:
| PolyAPI construct | Rust | Java |
|---|---|---|
int64 |
i64 |
long |
string |
owned UTF-8 String |
String with JSON Unicode semantics |
bool |
bool |
boolean |
[T] |
Vec<T> |
List<T> |
| record | generated Serde struct | generated immutable DTO/record |
| JSON | Serde/serde_json with generated validation |
Jackson with generated validation |
| async/runtime | Tokio | Java 21+ threads or virtual threads |
Rust arithmetic must use explicit wrapping_* operations, including summation
and the MIN / -1 division case. Java long operations must preserve Java's
two's-complement wrap behavior. Both targets evaluate operands left to right,
truncate division toward zero, and map division by zero to the same stable
PolyAPI runtime error.
Decoders must reject malformed JSON, missing required fields, additional record fields, wrong primitive types, and values outside declared constraints before endpoint instructions run. Framework exception text and target-language class names must never become public error messages.
Sanitizers and sinks are dispatched through target registries keyed by the IR intrinsic or capability. Selecting Rust or Java fails during compatibility checking when its registry lacks anything used by the document. Generated code must not replace an unsupported operation with a no-op or implicit sanitizer.
The Rust generator emits a deterministic Cargo workspace with generated models, validators, endpoint functions, security adapters, SeaORM entities, and a runtime entrypoint. It uses Tokio's Unix listener and Hyper's HTTP/1.1 connection serving over each accepted stream. It must not enable a TCP listener as a fallback.
Release builds are produced for an explicit target triple. The compiler records the Rust toolchain, lockfile digest, target triple, and executable digest in the bundle manifest. Cargo dependencies are locked. Panics are process faults, not ordinary API errors; validation, database, and transport failures must travel through generated result types and stable error mapping.
The runtime deletes no host path other than its assigned socket, handles termination by stopping accepts and draining in-flight requests, and exits non-zero if initial database validation or UDS binding fails.
The Java generator emits deterministic source plus a locked build for one self-contained executable JAR. Its baseline is Java 21 bytecode and a Java 21 or newer runtime. JSON DTOs are generated with Jackson configuration owned by the backend adapter, not global ambient defaults.
The server uses Netty's Linux epoll domain-socket transport and serves the same bounded HTTP/1.1 contract as the other targets. A Java backend fails startup when epoll UDS is unavailable; it never falls back to loopback TCP.
The JAR contains all non-JDK bytecode dependencies. The bundle manifest records the Java bytecode level, build lock/dependency digest, and JAR digest. Uncaught exceptions are process faults. Generated exception mapping handles validation, storage, and transport failures without returning stack traces, SQL, filesystem paths, or class names to clients.
A target is conforming only after the same built artifact that will be embedded passes:
- the canonical success, validation, arithmetic, and stable-error corpus;
- source, sanitizer, proof, forbidden-label, and sink negative cases;
- HTTP/1.1 parsing, body-size, timeout, cancellation, and malformed-input cases over a UDS;
- health quarantine, recovery, graceful shutdown, and child crash tests;
- first-write, same-language replay, every cross-language replay pair, conflict, concurrency, and database-unavailable tests from the Postgres profile;
- reproducible-build and manifest-digest verification for its artifact;
- negative compatibility tests proving unsupported IR features fail before emission.
The differential oracle compares semantic JSON values, status codes, stable error codes, headers declared by the API contract, database rows, and security flow decisions. Latency and target-identifying observability headers are not semantic equality fields.