Skip to content

Latest commit

 

History

History
195 lines (156 loc) · 10.2 KB

File metadata and controls

195 lines (156 loc) · 10.2 KB

PolyAPI backend execution profile 0.1

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.

Target registry

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.

Bundle and manifest contract

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.

Child process contract

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.

Private transport contract

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.

Language mappings

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.

Rust target profile

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.

Java target profile

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.

Differential conformance

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.