Skip to main content
Version: 0.1.0-alpha.4 (alpha)

Bounded HTTP application contract

latent:web/application@0.1.0 exports handle(request) -> response as a canonical async function. Its buffered-v1 profile is defined by WIT, ADR-0035 and the latent-ingress::http implementation. Generated host and guest bindings are available in latent-component-bindings::{host::web, web_guest}.

The standalone node's optional shared HTTP/TLS listener uses this mapping with durable exact-target routes. The node owns transport, authentication and connection limits; an application's WIT export creates no listener. Angular renderer adaptation remains #233.

Records and authority​

Request fieldMeaning
profileExactly buffered-v1.
methodget, head, post, put, patch, delete, or options. Raw HTTP tokens must have their usual uppercase spelling.
schemeActual transport http or https, supplied by the adapter.
authority, path, queryCanonical routing identity; absent and empty queries remain distinct.
headersOrdered records of lowercase names and opaque byte-list values.
media-typeOptional validated Content-Type, removed from the header list.
body-base64Canonical padded base64 of the complete raw HTTP body; empty body is "".

The response contains profile, status, headers, media-type, representation-length and body-base64. Response header names must already be lowercase. representation-length is an optional unsigned length permitted only for HEAD or status 304; it describes a representation without transferring it.

Principal, trace and deadline are host context, not request-body fields. The world imports latent:context/context@0.1.0; its versioned invocation-principal, trace-context, and deadline functions remain authoritative. The native TrustedContext supplies the same principal and trace to normal activation admission. The exchange's IncomingDeadline preserves its monotonic authority and diagnostic Unix projection. Header strings cannot select these values, grant capabilities, or extend a deadline.

Authorization credentials are processed by the trusted authenticator before dispatch. The application mapping removes Authorization, Proxy-Authorization, Forwarded, every X-Forwarded-* field, traceparent, tracestate and baggage. Trusted trace handling happens separately under node policy. X-LSF-* impersonation is rejected. Cookies remain application data and are not converted into a principal. A generic RPC caller can supply application values; the guest must still read identity through host context. HTTP provenance and HTTP-specific validation are provided by the actual HTTP adapter, not by the presence of an HTTP-shaped RPC payload.

Byte and allocation limits​

Limitbuffered-v1
Raw request / response body64 KiB / 256 KiB
Header fields / aggregate name and value bytes64 / 16 KiB
One header name / value64 / 4,096 bytes
Original path and query / authority8,192 / 255 bytes
Media type256 ASCII bytes
Native trusted context / claims and baggage8 KiB retained capacity / at most 32 entries each
Context string512 UTF-8 bytes; no controls
Invocation value frame / lexical depth / nodes2 MiB / 12 / 32,768
Shared mapping reservation per exchange4 MiB

HttpPool::new(maximum_exchanges, maximum_bytes) intersects both ceilings and rejects excess work immediately. It creates no worker, listener or preallocated body. Capacity is reserved before copying validated HTTP fields or decoding a guest response. The protocol parser must apply its own finite head/frame limits before constructing a borrowed RawHead; the mapping cannot undo allocations already made by an upstream parser.

Response decoding checks raw size, UTF-8, lexical depth and node count before serde scratch or recursive decoding. Closed typed records reject duplicate, missing and unknown keys. List lengths are checked before decoding excess elements. Base64 text has a finite encoded limit; padding determines the exact decoded length, which is checked before allocating the raw body. Whitespace, URL-safe alphabet substitutions, missing/excess padding and nonzero unused bits are rejected. This is RFC 4648 canonical base64, not an implicit byte-list codec extension.

The generic runtime uses the existing application/vnd.latent.wit-values.v1+json: one positional array, closed kebab-case records, enum strings, tagged options and decimal-string u64s. The explicit body string keeps the largest body out of a list of per-byte runtime values. A compatible tested Wasmtime configuration selects 2 MiB input/output, 32,768 nodes, 512 KiB strings, 4,096 collection items, 2 MiB hostcall fuel and 64 MiB conservative lifted-allocation allowance; decoded input remains limited to 16 MiB. Existing global defaults are not raised. These are independent runtime ceilings, not allocations reserved for every request. Operators must budget runtime transfers, guest memory, socket/TLS buffers and concurrency separately from the 4 MiB HTTP mapping reservation.

HTTP bodies and header values remain raw bytes. Body decoding never assumes UTF-8, parses a media type's content, decompresses a representation or expands a multipart body. The media grammar accepts type/subtype plus up to 16 unique token or quoted parameters; its initial profile excludes escaped quoted values, commas, semicolons inside quotes and non-ASCII media metadata.

HTTP and URL mapping​

The adapter completes and validates the entire header section before dispatch. HTTP/1.1 requires one Host; HTTP/2 may omit Host, but a supplied value must match the authority. Duplicate Host or Content-Length is rejected even when values match. Content-Length is a canonical unsigned decimal and must match the complete body. Without Content-Length, this HTTP/1.1 profile accepts an empty request body. GET and HEAD request bodies are empty. An incomplete, excessive or failed collection cannot be retried as a valid request on the same owner.

The mapper rejects Connection, Keep-Alive, Proxy-Connection, TE, Transfer-Encoding, Trailer and Upgrade. ADR-0039 permits the trusted listener to consume exactly one Connection field containing only close or keep-alive before mapping; arbitrary nominations still fail. The transport must reject folded headers, conflicting HTTP/2 pseudo fields, ambiguous framing and connection reuse after a framing failure before creating RawHead. RPC timeouts alone are not an HTTP parser or connection policy. These restrictions apply the framing boundary described by RFC 9112.

Header names use HTTP token bytes. Values permit opaque obs-text bytes, but reject C0 controls, DEL, CR/LF, tabs and leading/trailing spaces. An adapter may remove HTTP framing OWS exactly once before mapping. Repeated fields preserve order; the mapper does not comma-join them, choose a first/last value, or reinterpret Cookie pairs. Every Set-Cookie remains a separate response field, consistent with RFC 9110 field semantics.

Only origin-form targets are accepted. Scheme and DNS host case normalize; strict IPv4 and bracketed IPv6 spellings are supported. Default ports are removed. Userinfo, scoped IPv6 zones, trailing DNS dots, leading-zero ports and malformed host labels are rejected. Percent escapes use uppercase hex and unreserved bytes are decoded once. Path dot segments, repeated slashes, encoded path separators, encoded path percent signs, controls and fragments are rejected. Query order, repetitions, +, empty values and encoded reserved bytes remain data; a query is never reinterpreted as a routing path. Raw non-ASCII targets must be URI-encoded. Routing and security checks consume CanonicalTarget directly and never decode it a second time. This is the explicit application profile of RFC 3986 normalization.

Responses accept status 200 through 599. HEAD and statuses 204, 205 and 304 have empty bodies. The adapter derives Content-Length from the body for ordinary responses, emits zero for 205, omits it for 204, and uses the optional representation length for HEAD/304. Guests cannot supply Host, Content-Length, Content-Type, Server, Date, Via, Alt-Svc, forwarding/credential or reserved X-LSF-* response fields. Informational responses, CONNECT, TRACE, upgrades, trailers and streaming are outside this versioned profile.

Failure and delivery ownership​

OutcomeHTTP meaning while a writable response is owned
Valid application response, including 4xx/5xxPreserve the application's status and bounded content.
Invalid guest response, declared WIT error, trap, incompatible/corrupt dependencyFixed 502; never reflect the guest error or payload.
Unauthenticated / permission denied / missing targetFixed 401 / 403 / 404.
Invalid argument / state conflictFixed 400 / 409.
Admission, queue or resource exhaustion; unavailable route/providerFixed 503.
Activation deadline exceeded before the exchange's transport deadlineFixed 504.
Internal/unknown platform failureFixed 500.
Disconnect, expired exchange deadline, cancelled transportClose/abort delivery; no fabricated successful response.

Collection errors map to 400, unsupported methods to 405, request-body excess to 413 and aggregate header excess to 431, if bounded writable error capacity exists. No mapping implicitly retries an invocation, provider operation or partial write.

The affine ownership sequence is Collector -> Request -> Invocation -> Delivery. Raw-body access borrows its owner. There is no raw-Vec extraction or cloned response. The adapter must keep invocation ownership until the actual activation and input cleanup retire, propagate the disconnect signal to normal cancellation, and wait for that cleanup; signalling does not make an active cell free. Cancellation handles retain the shared reservation until they too are dropped.

Delivery exists before any response write. Mark headers only after their local write completes; advance body progress only by successfully written bytes. A successful finish() requires both headers and the complete body. Its receipt means completed local writes, not browser receipt, consumer processing or transactional commit. Failure after a partial write closes the exchange. Data fields are dropped before the reservation is refunded. Dormant applications acquire none of these owners.

Validation​

cargo test -p latent-ingress --locked covers goldens, repeated cookies/raw bytes, framing and URL attacks, closed records, size limits, partial writes, deadline and disconnect ownership, overload and reuse. Wasmtime unit tests derive actual value types from the authoritative WIT and check both goldens and maximum response encoding. Contract CI also builds the Rust web-contract fixture, executes the real ABI with the maximum body and near-limit headers, and observes the trusted host principal changing between fresh activations. The fixture is an ABI test; it does not add a package-provenance recipe or constitute an HTTP listener test.