=encoding UTF-8

=head1 NAME

PAGI::Spec - The base PAGI specification: application interface and core concepts

=head1 PAGI (Perl Asynchronous Gateway Interface) Specification

I<Note: code examples are illustrative; their style is a documentation
convenience, not a requirement of the specification.>

B<Version>: 0.4 (Draft)

=head2 Introduction

While work has been done to support asynchronous response handling in PSGI (the C<psgi.nonblocking> environment key), support for protocols with multiple input events (such as WebSockets) remained hacks. The use of non-blocking or asynchronous handling based on PSGI never took off. The PSGI specification hasn't changed since July 2013. Since then, asynchronous programming has taken off in many programming languages, including in Perl: although Perl does not natively support the C<async/await> pattern like Python or JavaScript, the module C<Future::AsyncAwait> implements the pattern since 24 January 2018 for Perl versions 5.16 and up.

Considering the above and the fact that similar specifications have existed for several years now for other languages, the time has arrived to create a fully asynchronous webserver gateway interface specification for Perl.

Perl does not natively support Python's C<async/await> pattern, but we can achieve similar functionality using B<Future::AsyncAwait> which provides C<async/await> syntax on top of C<Future.pm>. Below is how we translate key ASGI components into Perl.

=head2 Abstract

This document proposes a standard interface between network protocol servers (particularly web servers) and Perl applications, intended to support multiple common protocol styles (including HTTP/1.1, HTTP/2, and WebSocket).

This base specification defines the APIs by which servers interact with and run application code. Each supported protocol (such as HTTP) includes a sub-specification detailing how to encode and decode that protocol into structured messages.

=head2 Rationale

PSGI has worked well as a standard interface for synchronous Perl web applications. However, its design is tied to the HTTP-style request/response cycle, and cannot support full-duplex protocols such as WebSockets.

PAGI preserves a simple application interface while introducing a fully asynchronous message-based abstraction, enabling data to be sent and received at any time during a connection's lifecycle.

It defines:

=over

=item -

A standardized interface for communication between server and application.

=item -

A set of message formats per protocol.

=back

The primary goal is to support WebSockets, HTTP/2, and Server-Sent Events (SSE) alongside HTTP/1.1, while maintaining compatibility with existing PSGI applications through a transitional adapter layer.

=head2 Overview

PAGI consists of two main components:

=over

=item -

A I<protocol server> which manages sockets and translates network events into connection-level messages.

=item -

An I<application> which is invoked once per connection and communicates via asynchronous message passing.

=back

Applications are written as asynchronous subroutines using C<Future::AsyncAwait>. They receive a C<scope> describing the connection, and two coderefs, C<$receive> and C<$send>, which return Futures representing event input and output.

Unlike PSGI, PAGI applications persist for the entire connection lifecycle. They process incoming events from the server and emit outgoing events in response.

Two important concepts in PAGI:

=over

=item -

I<Connection scope>: A hashref describing the connection.

=item -

I<Events>: Hashrefs representing messages sent/received during the connection.

=back

=head2 Specification Details

=head3 Connection Scope

Each incoming connection causes the application to be invoked with a C<scope> hashref. Its keys include:

=over

=item -

C<type>: Protocol type, e.g., C<http>, C<websocket>

=item -

C<pagi>: A hashref containing:

=over

=item -

C<version> -- the core PAGI specification version, e.g. C<'0.4'>

=item -

C<spec_version> -- the protocol sub-spec version, e.g. C<'0.3'>. It may be
omitted only as permitted by that protocol specification; if omitted, use the
protocol's documented default.

=back

=item -

C<extensions>: A hashref in which the server advertises optional capabilities (see below).

=back

The C<extensions> hashref advertises optional server capabilities. Each present
key names an extension (e.g. C<tls>, C<websocket.http.response>) mapping to a
(possibly empty) hashref of extension data; applications check for a key's
presence before relying on it. An absent or empty C<extensions> means none are
available. See L<PAGI::Spec::Extensions>.

Clarification on PAGI version keys:

=over

=item -

C<version>: Signals the core PAGI specification version that governs the
runtime application interface and core semantics. This value B<must> be a
string.

=item -

C<spec_version>: Indicates the version for the specific protocol (such as HTTP
or WebSocket). Core and protocol versions are independent and need not be
equal. Each protocol specification MUST define the version represented by an
omitted C<spec_version>. Applications MUST use that protocol-defined default
and MUST NOT infer it from C<version>. A server that omits C<spec_version> is
declaring only the protocol-defined default and MUST NOT use behaviour
introduced by a later protocol version on that connection.

=item -

Additional keys may be defined per protocol specification (e.g., HTTP, WebSocket) and must be documented there.

=back

The scope describes the full lifetime of the connection, and persists until the connection is closed. Some protocols (e.g., HTTP) may treat each request as a distinct scope, while others (e.g., WebSocket) maintain one persistent scope for the entire session.

Specifically:

=over

=item -

HTTP treats each incoming request (for example, GET or POST requests) as a separate scope that exists only for the life of that request-response cycle. After the response is sent fully, the scope is discarded.

=item -

WebSocket connection establishes a single scope at connection initiation, persisting for the full duration of the continuous message exchanges until the WebSocket connection closes.

=back

Applications may need to wait for an initial event before sending any messages, depending on the protocol specification.

If the application does not recognize or support C<<< scope->{type} >>>, it MUST throw an exception. The PAGI server must handle such exceptions gracefully, by closing the connection promptly, optionally logging the issue and providing relevant protocol-specific error responses or messages.

This explicit requirement ensures that servers never assume application-protocol compatibility without explicit support declared by implementations.

=head3 Events

PAGI defines communication in terms of discrete events.

The C<type> key in each event must be a namespaced string of the form C<protocol.message_type>, such as C<http.request> or C<websocket.send>. This convention ensures clear protocol dispatching and avoids naming collisions.

Reserved C<type> prefixes include:

=over

=item -

C<http> -- standard HTTP events

=item -

C<websocket> -- WebSocket events

=item -

C<lifespan> -- process lifecycle events

=item -

C<pagi> -- reserved for future PAGI-defined system events

=item -

C<ext.> -- reserved for experimental or nonstandard events

=back

Custom user-defined protocols should avoid clashing with these prefixes.

    my $event = await $receive->();
    await $send->({ type => 'http.response.start', ... });

=head4 Permitted Event Data Types

The following types are permitted in event data structures:

=over

=item -

B<Strings> (Perl scalars):

=item -

B<Text strings>: Strings containing decoded Unicode characters. These must be explicitly encoded according to the relevant protocol before serialization or transmission.

=item -

B<Byte strings>: Strings containing opaque binary data. Their intended encoding or interpretation, if applicable, must be explicitly specified by the relevant protocol.

I<Note>: Do B<not> rely on Perl's internal UTF-8 flag (C<utf8::is_utf8>) to distinguish between text and binary strings. The protocol or application context must explicitly indicate the intent.

=item -

B<Integers>: Signed 64-bit integers. While Perl supports arbitrary-size integers, PAGI restricts integers to this range to ensure interoperability across serialization formats and programming languages.

Integers commonly stand in for boolean values (C<0> or C<1>). When a protocol flag is described as boolean it B<must> still use integers (never custom barewords) so transports remain consistent.

=item -

B<Floating-point numbers>: IEEE 754 double-precision floats. Special values (C<NaN>, C<Inf>, C<-Inf>) are B<not permitted>, due to inconsistent cross-platform and serialization support.

=item -

B<C<undef>>: Represents the absence of a value.

=item -

B<Arrayrefs>: Arrays containing only permitted data types.

=item -

B<Hashrefs>: Hashes with keys stored as B<byte strings>.

=item -

If keys represent textual data, they B<must be explicitly encoded into UTF-8 byte strings> by the application.

=item -

Values must contain only permitted data types.

=back

B<Note:> Booleans are intentionally omitted from the core spec to avoid ambiguity in Perl, which lacks a native boolean type. In contexts where ASGI would use C<true>/C<false>, PAGI protocol specifications will define expected values explicitly -- usually C<0> or C<1>, or by the presence/absence of a defined value.

=head3 Applications

At the server/application protocol boundary, a PAGI application is a single
code reference that accepts C<($scope, $receive, $send)> and returns a Future.
This is the only runtime application interface defined by PAGI.

Application startup and shutdown are expressed through the lifespan protocol
in L<PAGI::Spec::Lifespan>. Methods on an application-provider object are not
PAGI lifecycle hooks. Deployment tooling may define its own provider hooks,
but they do not replace or alter the PAGI lifespan protocol.

=head4 Application Providers and Loading

PAGI defines the runtime application interface, not how deployment tooling
discovers, configures, or constructs an application. To support applications
that need configuration or retain construction metadata, a general-purpose
application runner SHOULD accept both of these input forms:

=over

=item -

a native PAGI application code reference; or

=item -

an already-instantiated application-provider object that implements C<to_app>.

=back

An application-provider object is not itself a PAGI application. A runner
that accepts one MUST call C<to_app> exactly once per loaded provider instance,
before using it for connection dispatch, and MUST verify that the result is a
code reference. An exception from C<to_app>, or any other return value, is an
application-loading failure and MUST be reported before the runner begins
serving traffic. Reloading an application or constructing independent worker
instances constitutes a new load; request and connection dispatch do not.

In this convention, an application-provider object is an already-instantiated
blessed reference. A package-name string is not an application provider, and a
runner MUST NOT satisfy provider normalization by invoking C<to_app> as a class
method. Deployment tooling MAY accept module or class names as its own
discovery and configuration input, but it must first construct an object or
obtain a native PAGI code reference before entering this normalization step.

A value that is itself a code reference is already a native PAGI application,
even if it is blessed. It MUST be used as the application without calling a
C<to_app> method it may also provide.

A runner MUST NOT call C<to_app> once per request or connection. After
normalization, the server, middleware, and all per-connection dispatch use only
the resulting PAGI application code reference.

File names, module names, command-line expressions, configuration formats,
server construction, and runner plugin APIs are deployment-tool concerns and
are outside this specification. See L<PAGI::Spec::Server> for integration
guidance.

Applications B<must> throw an exception if the incoming C<<< scope->{type} >>> is not supported. This prevents the server from assuming a protocol is handled when it is not, and avoids ambiguity in multi-protocol deployments.

Each application is expected to run for the duration of a connection and may remain alive briefly after disconnect to perform cleanup. The interface is:

    use Future::AsyncAwait;

    async sub application {
        my ($scope, $receive, $send) = @_;
        if ($scope->{type} ne 'http') {
            die "Unsupported protocol type: $scope->{type}";
        }

        await $send->({
            type    => 'http.response.start',
            status  => 200,
            headers => [ [ 'content-type', 'text/plain' ] ],
        });

        await $send->({
            type       => 'http.response.body',
            body       => "Hello from PAGI!",
            more       => 0,
        });

        return;
    }

=over

=item -

C<$scope>: Hashref with connection metadata

=item -

C<$receive>: Coderef returning a C<Future> resolving to the next event

=item -

C<$send>: Coderef taking an event hashref and returning a C<Future>

=back

Each application is called per-connection, and remains active until the connection closes and cleanup completes.

=head3 Send Completion Contract

Every call to C<$send> returns a Future. Successful resolution means the server
has validated and finished consuming the event, including any
application-owned resource whose lifetime is tied to that send, and has
accepted the resulting output into its outbound processing path. The
application may release such a resource after the Future resolves.

Resolution does B<not> mean that bytes have reached the network socket, an
intermediary, or the client. It also does not mean that the complete response
has been delivered.

A server MAY keep the Future pending to apply outbound flow control. Awaiting
C<$send> is therefore the transport-independent mechanism by which an
application self-paces; awaiting a pending send suspends the application
coroutine, not the Perl thread. This contract applies whether or not the server
provides the optional C<pagi.transport> introspection object described by the
applicable protocol specification.

Before the connection closes, event-validation and event-resource failures
required by the applicable protocol specification MUST fail the returned
Future. After the connection has closed, C<$send> instead returns a
successfully resolved Future and performs no work: it neither delivers the
event nor signals closure. Applications detect closure through the protocol's
disconnect event and any applicable connection-state object.

=head3 Application Completion Contract

B<The application's returned Future represents request completion.> When this Future resolves, the server considers the response finished and may close the connection, write access logs, or handle the next request.

Applications B<must> await all C<$send> calls before their Future completes:

    # CORRECT: await all sends
    async sub app {
        my ($scope, $receive, $send) = @_;
        await $send->({ type => 'http.response.start', status => 200, headers => [] });
        await $send->({ type => 'http.response.body', body => 'Hello' });
    }

    # WRONG: send not awaited, app Future resolves before send completes
    async sub app {
        my ($scope, $receive, $send) = @_;
        $send->({ type => 'http.response.body', body => 'Hello' })->retain;
        return;  # App Future resolves immediately, send may not complete!
    }

Work scheduled via C<<< ->retain >>> or other fire-and-forget patterns will race with connection cleanup and produce undefined behavior. The server has no visibility into retained Futures - they exist outside the application's Future tree.

B<The value the Future resolves to is inert.> Completion is signalled by the
Future resolving, errors by it failing, and every client-visible result by
events sent through C<$send> (with connection facts such as C<response_started>
exposed on the C<pagi.connection> object). The server assigns the resolved
value no meaning and B<must not> act on it; a conformant application resolves to
nothing meaningful.

Frameworks layered above the gateway B<may> use the resolved value for their own
composition -- for example, returning a response object up an internal call
chain -- but B<must> convert any such value into protocol events at the gateway
boundary. A framework value B<must not> escape to the server as the
application's result.

If an C<http> application's Future resolves without any C<http.response.start>
having been sent -- and the client is still connected -- the client is left
waiting for a response that will never arrive. As a last-resort B<backstop> the
server produces a response (a C<500>) and logs it; see
L<PAGI::Spec::Www/"Application Produced No Response">. This is a backstop, B<not> a
completion policy: which terminal response to produce (a C<404>, a C<204>, an
error page, ...) is the application's or framework's concern, and a well-behaved
application always produces one before its Future resolves. PAGI exposes only the
observer-independent fact that no response was started (the C<response_started>
predicate on the C<pagi.connection> object); it makes no judgment about
whether a response is "complete". An application that does not handle a scope at
all should decline by raising (see L</Applications>), which the server surfaces
the same way. (For the lifespan scope, see L<PAGI::Spec::Lifespan>.)

B<Use C<PAGI::Middleware::Lint>> during development to detect incomplete responses.

=head3 Legacy Applications

Legacy (PSGI) applications are not async and follow a synchronous interface. Support for these can be implemented via a compatibility adapter.

Adapters must call the PSGI app once with C<$env> and transform the response into PAGI event messages.

=head3 Protocol Specifications

Each protocol defines:

=over

=item -

Scope format

=item -

Event types for C<$receive> and C<$send>

=back

Common examples:

=over

=item -

C<http.request>, C<http.response.start>, C<http.response.body>

=item -

C<websocket.connect>, C<websocket.receive>, C<websocket.send>

=back

The C<type> field in scope and events identifies the protocol and message:

    $event->{type} eq 'http.request';
    $scope->{type} eq 'websocket';

Applications B<must> throw an exception if the protocol is unknown.

Current protocol specifications:

=over

=item -

L<HTTP, WebSocket and SSE|PAGI::Spec::Www>

=item -

L<Lifespan|PAGI::Spec::Lifespan>

=back

=head3 Middleware

PAGI middleware wraps an application:

    use Future::AsyncAwait;

    sub middleware {
        my ($app) = @_;
        return async sub {
            my ($scope, $receive, $send) = @_;
            my $modified_scope = { %$scope };
            $modified_scope->{custom} = 1;

            my $result = eval { await $app->($modified_scope, $receive, $send) };
            if (my $error = $@) {
                # Middleware MUST propagate exceptions clearly without silently swallowing them.
                # Optionally, middleware can log or handle the error explicitly before rethrowing.
                warn "Middleware encountered an application error: $error";
                die $error;
            }
            return $result;
        };
    }

A middleware propagates the inner application's B<Future> -- its completion and
any exception -- not a value. The C<return> hands the inner application's
resolution outward so completion and errors flow unchanged; the resolved value
itself is inert (see L</Application Completion Contract>). A middleware that
needs to observe or rewrite the response does so by wrapping C<$send> or
C<$receive>, never by reading the return value.

Middleware B<must not> mutate the original C<$scope> in place; clone before
modifying:

    my $inner = { %$scope };
    $inner->{'myapp.x'} = 1;
    await $app->($inner, $receive, $send);

The clone is shallow, and intentionally so: top-level keys set on the clone are
private to this layer and the layers below it, while values reached I<through> a
reference -- the C<pagi.connection> object, the lifespan C<state> namespace --
are shared across all layers. This keeps each middleware's scope edits
B<downward only>. See L<PAGI::Building/MIDDLEWARE> for the sharing model, why the
clone is shallow rather than deep, and patterns for sharing state across layers.

=head3 Cancellation and Disconnects

If the server detects client-driven cancellation (e.g., HTTP/2 resets or WebSocket disconnects), it B<must> deliver the appropriate disconnect event to the application:

=over

=item -

HTTP: C<<< { type => 'http.disconnect' } >>>

=item -

WebSocket: C<<< { type => 'websocket.disconnect' } >>>

=item -

SSE: C<<< { type => 'sse.disconnect' } >>>

=item -

Other protocols: see their respective specifications for the canonical event name.

=back

Applications receiving these events should immediately halt unnecessary work, optionally send minimal acknowledgments if the protocol allows it, and perform cleanup for open resources or background tasks. Servers must also ensure timely cleanup after the application finishes its cancellation handling.

=head3 Error Handling

Before the connection closes, a server MUST reject an outgoing event by
returning a failed Future from C<$send> if:

=over

=item -

Events are missing required fields

=item -

Event fields are of the wrong type

=item -

The C<type> field is unrecognized

=back

Applications should reject malformed incoming events by failing their
application Future.

Extra fields in events B<must not> cause errors -- this supports forward-compatible upgrades.

The post-close behaviour of C<$send> is defined in L</Send Completion
Contract>: it is a successful no-op and does not report closure. Applications
use the protocol's disconnect event and, for HTTP, the C<pagi.connection> state
object instead.

=head3 Extensions

Servers may expose additional features via the C<extensions> key in the scope:

    $scope->{extensions} = {
        fullflush => {},
    };

Applications may send new events such as:

    await $send->({ type => 'http.fullflush' });

Only if the server declares support in C<extensions>.

=head3 Strings and Unicode

All keys in scope and event hashrefs B<must> be strings that are valid UTF-8 when interpreted as bytes. The UTF-8 flag is not required, but the keys must decode cleanly as UTF-8.

Byte content (e.g., body payloads) must be passed as Perl scalars B<without> the UTF-8 flag set. Applications are responsible for encoding/decoding appropriately.

=head2 Version History

=over

=item -

0.4 (Draft): Distinguished the runtime PAGI application from an
application-provider object and canonized one-time-per-load C<to_app>
normalization by general-purpose application runners. Lifecycle remains part
of the lifespan protocol rather than an inferred provider-object hook. Core
and protocol versions evolve independently; each protocol now owns the
compatibility default for an omitted C<spec_version>.

=item -

0.3 (Draft): Removed C<pagi.features> (superseded by the C<extensions>
mechanism). See L<PAGI::Spec::Www> for the HTTP/WebSocket/SSE 0.3 changes.

=item -

0.2 (Draft): Loop-agnostic applications, improved event validation, clarified
application completion contract, added extension mechanism documentation

=item -

0.1 (Draft): Initial PAGI version, modeled after ASGI 3.0

=back

=head2 See Also

The PAGI specification spans several documents:

=over

=item -

L<PAGI::Spec::Www> - HTTP, WebSocket, and SSE message formats

=item -

L<PAGI::Spec::Lifespan> - startup and shutdown lifecycle protocol

=item -

L<PAGI::Spec::Extensions> - the server extension mechanism

=item -

L<PAGI::Spec::Tls> - TLS connection information extension

=item -

L<PAGI::Spec::Server> - server and application-runner integration guidance

=back

See L<PAGI> for an overview of the ecosystem and L<PAGI::Tutorial> for a guided
introduction to the protocol.

=head2 Copyright

This document is placed in the public domain.
