# This file documents the revision history for Perl extension PAGI

0.002008 - 2026-09-01

  [Specification]
  - HTTP/WebSocket/SSE sub-spec 0.4 -> 0.5. These are new requirements, not
    clarifications, so $scope->{pagi}{spec_version} moves with them. The core
    spec is unchanged.
  - The terminal response event is defined as what it asserts: the
    application's claim that the response is complete (RFC 9110 S6.1), which
    the server renders as the chunked terminator or END_STREAM. The existing
    prohibitions follow from that.
  - The ban on synthesizing terminal framing binds every producer, not only
    the server. Middleware that buffers and re-emits a body MUST NOT complete
    an incomplete stream, nor derive an ETag, Content-Length, or other
    completeness-dependent metadata from the bytes it happened to see.
  - Says which of the two signals answers which question: absence of the
    terminal event governs what may be forwarded or synthesized;
    disconnect_reason governs only whether to raise or log. The previous
    wording left it to inference, and every consumer inferred it the same way
    and wrong.
  - A response body is either inline events or one file/fh event, never both.
    Mixing them leaves a body-transforming intermediary unable to honour the
    encoding it already declared; the server now fails such a send.
  - An intermediary MUST examine the response head before transforming a body
    or attaching metadata derived from one, and MUST NOT invalidate what that
    head promised -- trailers => 1 commits the server to chunked framing, and
    a 206 body is a range rather than the representation.
  - An intermediary re-emitting a buffered event MUST preserve its other
    keys, including more, whose default of 0 makes an omission an assertion
    of completeness.

  [What may break]
  - Sending a file/fh body after inline body bytes. Undefined before, so it
    worked; a conforming server now fails that send. Send the whole body
    inline instead, or put the leading bytes in the file.
  - Relying on an intermediary to finish an incomplete response. Middleware
    may no longer synthesize a terminal event, ETag or Content-Length on an
    application's behalf, so such a response now stays observably incomplete.
  - Middleware that transforms bodies without consulting the response head
    is now non-conforming, even where it happens to work.
  - Not affected: complete responses, file/fh alone, inline streaming to
    completion.

0.002007 - 2026-08-29

  [Specification clarifications - resolved ambiguities]
  - Connection State: futures returned by disconnect_future are
    cancellation-isolated -- cancelling one (directly, or as the losing
    component of a combinator such as Future->wait_any) never disturbs
    the server's disconnect processing or any other returned Future.
    Makes explicit what the accessor's definition already promises; the
    racing example is correct as written.

0.002006 - 2026-08-28

  [Specification - pending I/O at disconnect]
  - Core spec 0.4 -> 0.5, HTTP/WebSocket/SSE sub-spec 0.3 -> 0.4: defined
    settlement of send and receive Futures still pending at disconnect
    (settle by resolving, never cancel), the disconnect-transition
    ordering invariant, loop-deferred delivery of disconnect
    notifications, and the corrected HTTP/2 RST_STREAM mapping. See the
    Send Completion Contract in PAGI::Spec and State Transition Order in
    PAGI::Spec::Www.

0.002005 - 2026-08-26

  [Specification clarifications - resolved ambiguities]
  - Consolidated the connection-level-headers rule: PAGI defines no header
    exceptions of its own -- where the HTTP RFCs carve one out (te with the
    trailers token per RFC 9113, forwarded normalized; the Connection:
    upgrade companion an Upgrade sender owes per RFC 9110), the server
    implements the RFC rather than the blanket strip. Replaces the separate
    426-companion paragraph and corrects the blanket "strips all of these"
    overstatement; no server behavior change.

0.002004 - 2026-08-25

  [Specification clarifications - resolved ambiguities]
  - Connection-level headers: over HTTP/1.1, when an application response
    carries an Upgrade header (e.g. on a 426 Upgrade Required), the server
    must include 'upgrade' among the tokens of the Connection header it
    supplies, per RFC 9110's requirement on any Upgrade sender. The
    application never sends Connection: upgrade itself; the server
    completes the pair. Closes the conflict between the
    server-owns-connection-headers rule and RFC 9110's 426 requirements.
  - The incomplete-response definition now says explicitly that it covers an
    application Future that fails with an exception, not only one that
    resolves, after http.response.start and before the terminal event. No
    behavior change: the neighboring clauses already treat exceptions
    identically, and the reference server conforms both ways.

0.002003 - 2026-08-24
  [Specification clarifications - resolved ambiguities]
  - http.response.body payload keys (body / file / fh) are at-most-one per
    event; omitting all three means an empty body chunk.
  - Defined HEAD response semantics: the application responds as it would to
    GET and the server suppresses the body on every HTTP version -
    Content-Length passes through, no chunked framing, file/fh bodies are
    never opened or statted, trailers are validated and discarded.
  - A response left incomplete after http.response.start forces abnormal
    closure on every transport: the server never synthesizes the missing
    terminal framing, the client observably sees truncation (HTTP/1.1 close
    without the chunk terminator; HTTP/2 RST_STREAM such as INTERNAL_ERROR),
    on_disconnect fires with reason server_error, and on_complete never does.
  - SSE detection is an exact text/event-stream media-range match with
    q > 0; wildcards and explicit q=0 refusals never signal SSE.
  - Lifespan strict mode (operator-configured startup-failure-is-fatal) is
    sanctioned; a decline-tolerant mode remains the required default; an
    "off" switch that skips the protocol entirely is expressly nonconforming.
  - Over HTTP/1.1, trailers exist only in chunked framing: a server must
    reject http.response.trailers on a Content-Length-framed response by
    failing the send Future, never by silently dropping the trailers.
  - Delivery defines completion: an application exception raised after the
    response's terminal event does not un-complete the request - on_complete
    fires, disconnect_reason stays undef, the server should log the exception
    and may close the connection.

  [Specification clarifications - send and delivery contracts]
  - Sends are sequential: an application must not issue a new send on a
    connection before the previous send's Future resolves; the effect of
    overlapping sends is unspecified.
  - file/fh delivery failure is fail-don't-mutate: a missing or unreadable
    file, or a read or seek failure, fails that send's Future and leaves the
    response sequence state as it was, so the application may recover; once
    body bytes reached the wire the incomplete-response rules govern. The
    failed Future is the application's notification channel.
  - Connection-level and framing response headers belong to the server:
    HTTP/2 strips the connection-specific set from application responses
    (RFC 9113 forbids them); HTTP/1.1 must ignore or strip
    application-supplied Transfer-Encoding and Connection, continuing the
    WSGI/ASGI hop-by-hop lineage.
  - WebSocket denial events after websocket.accept, and sends after the
    application's own websocket.close, fail the send Future (the
    fail-don't-mutate rule); only a transport-initiated closure makes a
    subsequent send a race-friendly no-op.
  - Exceptions raised by registered callbacks (on_complete, on_disconnect,
    and the pagi.transport callbacks) must not prevent remaining callbacks
    from running or disturb server processing; servers should log them.

  [Documentation]
  - Expanded THE PAGI ECOSYSTEM in PAGI.pm: a Built on PAGI section
    covering Thunderhorse, PAGI::FastAPI, WebDyne, PAGI::Nano, and
    Uniform::HTMX::PAGI, and an Announcements section pointing at the
    pagi-announce mailing list.
  - Reworked UTF-8 HANDLING OVERVIEW in PAGI.pm to lead with the
    PAGI-Tools version of the example (PAGI::Request/PAGI::Response,
    where decoding and encoding are encapsulated at the boundary) and to
    present the raw-interface version as the contract toolkits implement
    rather than a recommended application style; the raw example now
    percent-decodes with URI::Escape and handles '+' as space.

  [Specification additions]
  - Widened the idle_timeout and keepalive_timeout disconnect-reason
    definitions to the senses in real-world use: mid-stream idleness on a
    quiet WebSocket or SSE session, and a WebSocket keepalive ping that
    received no pong.
  - Added 503 Service Unavailable (admission control, with Retry-After) to
    the generated-response catalog.
  - Listed pagi.transport in the HTTP, WebSocket, and SSE scope-key tables,
    where it was specified but unlisted.
  - Added optional WebSocket scope keys max_frame_size and
    max_receive_queue so applications can size client framing and apply
    their own flow control against the limits the server actually enforces.

0.002002 - 2026-08-21
  [Specification 0.4 Draft]
  - Distinguished the canonical runtime PAGI code reference from an
    already-instantiated application-provider object. General-purpose runners
    should accept either; package-name strings and class-method to_app dispatch
    are not provider forms. Each loaded provider instance is normalized exactly
    once before dispatch, and only the resulting code reference crosses the
    runtime boundary.
  - Lifecycle handling remains part of the lifespan protocol and is not
    inferred by PAGI from methods on application-provider objects; deployment
    tools remain free to define their own non-protocol hooks.
  - Core and protocol versions now explicitly evolve independently. An omitted
    pagi.spec_version uses the default owned by the applicable protocol rather
    than the core version; WWW and Lifespan both use 0.1 as their conservative
    compatibility default.
  - Revised PAGI::Spec::Server to version 0.2. PAGI no longer prescribes a
    new(app => ...) constructor or run method for conforming servers; server
    construction and plugin compatibility belong to runner-specific APIs.

  [Specification clarifications]
  - Centralized the $send Future contract: successful resolution means the
    server has finished consuming the event and any tied application resource,
    and accepted its output for outbound processing; it does not mean client
    receipt or complete-response delivery. Before close, validation and
    resource failures fail the Future; post-close sends are successful no-ops,
    and pending send Futures carry transport-independent backpressure even
    without optional pagi.transport introspection.

0.002001 - 2026-06-30
  [Specification updates]
  - Application/middleware return value is now specified as inert: the server
    assigns the resolved value no meaning and acts only on the Future
    (completion/errors) and on events plus connection facts. Frameworks may use
    return values for their own composition but must convert them to events at
    the gateway boundary; a value must not escape to the server as the result.
  - Middleware section: clarified that middleware propagate the inner Future
    (not a value) and observe/rewrite responses by wrapping $send/$receive;
    removed a stranded comment from the example.
  - Added the sse.close send-event: an explicit, application-initiated end to an
    SSE stream (the SSE analog of http.response.body{more=>0}), ending it
    immediately and decoupled from the application returning. Optional reason
    field is server-side metadata only -- never transmitted to the client (SSE
    has no close frame, unlike websocket.close). After sse.close, further
    sse.send/sse.comment/sse.keepalive MUST raise; sse.close is idempotent.
    Returning after the final sse.send remains a valid way to end a stream.
  - Added sse.http.response.start / sse.http.response.body send-events: before
    sse.start, an application MAY decline the stream and return a normal HTTP
    response (404, 401, a 204 to stop EventSource reconnection, a redirect, ...).
    Namespaced under sse. (mirroring the websocket.http.response denial events) so
    an sse scope only carries sse.* events. First-send-wins: after sse.start these
    are invalid; after sse.http.response.start the stream events are invalid (both
    MUST raise). Unlike the WebSocket denial extension this is a core capability
    (no event-stream fallback exists), so every conforming server MUST support it.

  [Documentation]
  - Cookbook: new SERVER-SENT EVENTS recipe "Ending a stream: return vs
    sse.close" -- the two termination paths (identical on the wire), the
    server-side-only reason, and observing close events by wrapping $send.
  - Tutorial: new Part 2 section "2.8 Middleware: Wrapping an Application"
    teaching the pure $app -> $app pattern (scope cloning, wrapping
    $send/$receive, short-circuiting, error propagation, the inert return
    value, and stacking). UTF-8 and async-pitfalls sections renumbered to
    2.9 and 2.10.

0.002000 - 2026-06-26
  [Distribution]
  - The PAGI distribution is now the specification: PAGI.pm plus the
    PAGI::Spec::* documentation. The reference server and its runner
    (PAGI::Server::Runner) moved to the PAGI-Server distribution, and the
    application toolkit (middleware, apps, endpoints, request/response,
    test utilities) moved to the PAGI-Tools distribution. Git history
    preserved.
  - The PAGI::Spec::* documents are now authored directly as POD. The
    previous markdown-to-POD build step (and its App::sdview dependency)
    has been removed; the spec POD is the source.
  - Expanded the learning material: PAGI::Cookbook (worked, runnable
    recipes), examples/mini-framework (a complete web framework on PAGI in
    about fifty lines), PAGI::PSGI (a migration guide for people coming
    from PSGI), PAGI::Building (a guide for framework authors), and a
    committed README.md. All example code targets Perl 5.18.
  - The spec modules are pure documentation. For backward compatibility
    during the transition, PAGI pulls in PAGI-Server and PAGI-Tools as
    runtime dependencies, so existing `requires 'PAGI'` dependents keep
    getting the server and toolkit. Depend on PAGI::Server / PAGI::Tools
    directly; this convenience dependency will be removed in a future
    release.

  [Specification updates]
  - pagi.connection gains response_started (MUST) and response_complete
    (SHOULD). response_started is true once http.response.start is emitted for
    the scope -- server-set, read-only, and producer-agnostic (an application
    response and a server-synthesized backstop both set it; the terminal
    callbacks carry the clean-vs-abnormal outcome). Because it lives on the
    shared pagi.connection object it is observable across the middleware stack,
    unlike a per-layer-copied scope scalar. Adds the per-request scoping note
    for connection (HTTP/1.1/2/3; SSE as its own scope type).
  - Documented scope shallow-clone semantics. Middleware clone the scope for
    downward-only isolation: top-level keys are private to each layer, while
    referenced values (the pagi.connection object, lifespan state) are shared,
    so a plain top-level scalar does not propagate -- share per-request state
    through a reference. PAGI::Spec states the normative minimum; the model is
    detailed in PAGI::Building, with shorter notes in PAGI::PSGI and
    PAGI::Tutorial.
  - Header byte safety is now the server's duty. On emission a server MUST
    reject (fail the send) any header name or value containing CR, LF, or NUL
    (and control characters in names) rather than forwarding or silently
    rewriting it (RFC 9112 S11.1, RFC 9110 S5.5). This generalizes the existing
    sse.send field-newline rule to all header emission and lets app-side
    containers such as PAGI::Headers hold values as opaque bytes.

0.001023 - 2026-05-21
  - Fixed incorrect metadata and package version information
  - POD fixes.

0.001022 - 2026-04-06
  [New Features — PAGI::Context]
  - New PAGI::Context factory + base class with protocol-specific subclasses
    (PAGI::Context::HTTP, PAGI::Context::WebSocket, PAGI::Context::SSE)
  - Factory inspects $scope->{type} and returns the appropriate subclass
  - Extensible via overridable _type_map and _resolve_class methods
  - Shared methods on all context types: scope accessors, stash, session,
    state, connection state, header lookup, path_params/path_param
  - has_session method for safe session availability checks
  - HTTP context: lazy request/response accessors, method accessor
  - WebSocket context: lazy websocket accessor
  - SSE context: lazy sse accessor
  - Shortcut aliases: req (request), resp (response), ws (websocket)
  - receive/send raw protocol escape hatches with POD examples

  [Breaking Changes — Endpoint Handler Signatures]
  - PAGI::Endpoint::Router now injects $ctx instead of ($req, $res) / ($ws) / ($sse)
  - PAGI::Endpoint::HTTP verb methods now receive ($self, $ctx) instead of ($self, $req, $res)
  - PAGI::Endpoint::WebSocket callbacks now receive ($self, $ctx, ...) instead of ($self, $ws, ...)
  - PAGI::Endpoint::SSE callbacks now receive ($self, $ctx, ...) instead of ($self, $sse, ...)
  - Endpoint middleware receives ($self, $ctx, $next) instead of ($self, $req, $res, $next)
  - request_class/response_class/websocket_class/sse_class replaced by context_class
  - context_class method on all endpoint types (defaults to 'PAGI::Context')

  [New Features — PAGI::Stash]
  - New PAGI::Stash standalone helper for per-request shared state
  - Strict get() that dies on missing keys with helpful error messages
  - Scope-based constructor works with any object that has ->scope method
  - Removed stash method from protocol helpers in favor of PAGI::Stash

  [Improvements — PAGI::Session]
  - Constructor refactored: new (scope-only) and from_data (raw hashref)
  - Added data method for raw hashref access
  - Chaining support on set/delete methods
  - Aligned error messages with PAGI::Stash conventions

0.001021 - 2026-04-01
  [New Features — Unix Domain Socket Support (Experimental)]
  - Unix domain socket listening via `socket` constructor option or --socket CLI flag
  - Socket file permissions via `socket_mode` option or --socket-mode CLI flag
  - Stale socket cleanup (unlink before bind) and cleanup on graceful shutdown
  - Restrictive umask during socket bind (CVE-2023-45145 mitigation)

  [New Features — Multi-Listener Support (Experimental)]
  - `listen` constructor option: array of listener specs for simultaneous endpoints
  - --listen CLI flag (repeatable): auto-detects TCP (host:port) vs Unix socket (path)
  - Per-listener scope: Unix sockets omit `client` from scope, set `server` to [path, undef]
  - Works with both single-worker and multi-worker modes
  - _pause_accepting now iterates all listeners under EMFILE conditions

  [New Features — FD Reuse and systemd Socket Activation (Experimental)]
  - PAGI_REUSE env var protocol for fd inheritance across exec (Mojo-inspired)
  - Inherited fd matching: listener specs matched to inherited fds by address
  - `local $^F = 1023` prevents close-on-exec on listen socket fds
  - Automatic LISTEN_FDS/LISTEN_PID detection for systemd socket activation
  - Inherited Unix socket files preserved on shutdown (systemd/parent owns them)

  [New Features — USR2 Hot Restart (Experimental)]
  - USR2 signal triggers zero-downtime master re-exec in multi-worker mode
  - Old master fork+execs new master; fds inherited via PAGI_REUSE
  - New master waits for worker heartbeats, then sends TERM to old master
  - Old master drains connections and exits; sockets never close
  - Idempotent: duplicate USR2 during restart is a no-op
  - Failure-safe: old master continues if new master fails to start

  [Bug Fixes]
  - Guard $scope->{client} access in 5 middlewares (AccessLog, Maintenance,
    ReverseProxy, RateLimit, WebSocket::RateLimit) to prevent autovivification
    on Unix socket connections
  - Validate --socket-mode is a 4-digit octal value (reject ambiguous input)
  - Harden --listen parser: paths starting with / or . always treated as Unix
    sockets (prevents mis-parsing paths containing colons)
  - Runner load_server omits host/port when socket or listen is provided

  [Test Fixes]
  - Use condition-based waiting in HTTP/2 SSE keepalive test (eliminates flakiness)
  - Gate timing-sensitive multiworker/heartbeat tests behind RELEASE_TESTING

0.001020 - 2026-03-16
  [Security Hardening]
  - Reject requests with both Transfer-Encoding and Content-Length (RFC 9112)
  - Validate Transfer-Encoding per RFC 9112 Section 6.1 (chunked must be final)
  - Validate trailer headers for CRLF injection
  - Validate SSE event/id/retry fields against newline injection
  - Prevent code injection via loop_type parameter (block eval, not string eval)
  - Fix TLS config to allow TLS 1.3 negotiation (SSL_version trailing colon)

  [Session Middleware Refactor]
  - Pluggable State/Store architecture for PAGI::Middleware::Session
  - State classes: Cookie (default), Header, Bearer, Callback
  - Store classes: Memory (default, dev/test), with async Future-based interface
  - PAGI::Session standalone helper with strict get() (dies on missing key)
  - PAGI::Session accepts raw data, scope hashref, or object with ->scope
  - Bulk operations: set(k1 => v1, k2 => v2), delete(@keys), slice(@keys), clear()
  - Session destroy lifecycle: deletes from store, clears cookie via State::clear()
  - Session regenerate lifecycle: generates new ID, deletes old, preserves data
  - Idempotency: middleware skips if pagi.session already in scope
  - Reserved keys (_destroyed, _regenerated) documented as middleware-consumed flags
  - Store::Cookie extracted to separate CPAN dist (PAGI-Middleware-Session-Store-Cookie)

  [Bug Fixes]
  - disable_tls skips TLS setup instead of dying
  - Recompile access log formatter on configure() changes
  - Weaken $self in _pause_accepting timer closure (reference cycle fix)
  - Replace Future::IO->load_impl('IOAsync') with use Future::IO::Impl::IOAsync
    (fixes compatibility with Future::IO 0.22+)

  [Cleanup]
  - Remove unused _log_connection_stats method

0.001019 - 2026-02-19
  [New Features]
  - Router: rewrite _compile_path() as tokenizer with proper regex escaping,
    so literal path segments containing regex metacharacters (e.g., /foo.bar)
    are matched literally instead of as patterns
  - Router: inline constraint syntax {name:pattern} for path parameters
    (e.g., /user/{id:\d+}) validated during dispatch
  - Router: chainable constraints() method for applying regex constraints
    to named path parameters after route definition
  - Router: any() multi-method matcher supporting wildcard ('*') and
    explicit method lists (e.g., any [qw(GET POST)] => '/path' => $handler)
  - Router: named routes and uri_for() now support {name:pattern} syntax
    and any() routes
  - Router: group() for organizing routes under a common prefix, with three
    forms: callback (sub), router-object, and string (auto-require). Supports
    middleware, nesting, named route namespacing via as(), and conflict detection
  - Router: mount() now accepts a string class name with auto-require and
    to_app dispatch
  - SSE over HTTP/2 with streaming DATA frames, keepalive comments, and
    disconnect handling
  - Add PAGI::Utils::Random module for cryptographically secure random bytes
  - Rate limiter: periodic cleanup and safety valve for expired buckets
  - CORS middleware: warn when configured with wildcard origins and credentials

  [Security Fixes]
  - Fix XSS in Debug middleware panel by escaping scope values in HTML output
  - Add max_chunk_size limit (default 1MB) to HTTP/1.1 chunked transfer
    parser to prevent denial-of-service via unbounded chunk sizes
  - Use cryptographically secure random bytes (via /dev/urandom with
    Crypt::URandom fallback) in RequestId, CSRF, and Session middleware
    instead of rand()
  - Default session cookie SameSite=Lax to prevent CSRF via cross-site requests
  - Fix double URL-decoding in Static middleware that could allow path traversal

  [Bug Fixes]
  - Fix HTTP/2 path decoding to match HTTP/1.1 decoding pipeline
  - Fix HTTP/2 WebSocket path decoding to match HTTP/1.1 pipeline
  - Require Net::HTTP2::nghttp2 0.007+ for HTTP/2 support (was unversioned)

  [Improvements]
  - Extract _format_sse_event() and _format_sse_comment() helpers and
    protocol-abstracted keepalive writer for consistent SSE formatting
    across HTTP/1.1 and HTTP/2

0.001018 - 2026-02-15
  [Bug Fixes]
  - Skip HTTP/2 subtests when Net::HTTP2::nghttp2 is not installed, fixing
    CPAN tester failures on systems without optional nghttp2 dependency

0.001017 - 2026-02-11
  [New Features]
  - HTTP/2 support (experimental). Requires Net::HTTP2::nghttp2. Enable with
    --http2 flag. Supports ALPN negotiation (h2/http1.1 over TLS), cleartext
    h2c via connection preface detection, WebSocket over HTTP/2 (RFC 8441),
    streaming responses with backpressure, and configurable protocol settings
    (h2_max_concurrent_streams, h2_initial_window_size, etc.)
  - Worker heartbeat monitoring for multi-worker mode. Parent process detects
    workers with blocked event loops via Unix pipe heartbeat and replaces them
    with SIGKILL + respawn. Default 50s timeout (heartbeat_timeout option,
    --heartbeat-timeout CLI flag). Only detects event loop starvation — async
    handlers using await are unaffected regardless of duration.
  - Custom access log format strings (--access-log-format). Supports atoms
    like %a (address), %s (status), %D (duration μs), %b (body size).
    Enables structured JSON logging via format string.
  - Track response body size in access log (%b atom)

  [Bug Fixes]
  - Fix TLS performance cliff at 8+ concurrent connections caused by
    per-connection SSL context creation parsing the entire CA bundle.
    Shared SSL context via SSL_reuse_ctx gives ~26x throughput improvement.
  - Fix TLS in multi-worker mode — workers now SSL-upgrade connections
    in on_stream rather than relying on listener-level TLS
  - Fix SSE wire format to handle CRLF, LF, and bare CR line endings
    per the SSE specification
  - Fix multi-worker shutdown escalation — SIGKILL timer now fires
    correctly when workers don't exit after SIGTERM
  - Fix multi-worker parameter pass-through for server options
  - Fix flaky multi-worker TLS test

  [Improvements]
  - Add on_ssl_error callback to TLS listener setup for cleaner error
    handling (suppresses "EV: error in callback" noise)
  - Cache Server header string to avoid per-response interpolation
  - Merge double header loop in serialize_response_start
  - Cache access log timestamp at per-second granularity
  - Cache client_host in access log to avoid per-request getpeername syscall
  - Skip HTTP/2 tests when Net::HTTP2::nghttp2 is not installed
  - Skip SSE tests when Future::IO is not installed
  - Remove unused Sys::Sendfile recommendation from cpanfile

0.001016 - 2026-01-31
  [New Features]
  - Add query parameter parsing to PAGI::SSE: query_params(), query_param(),
    raw_query_params(), raw_query_param() methods mirroring PAGI::Request
    and PAGI::WebSocket APIs
  - pagi-server now auto-configures Future::IO for IO::Async if installed,
    enabling seamless use of Future::IO-based libraries (Async::Redis, etc.)
    and PAGI::SSE->every(). Reports configuration in non-production mode.

  [Breaking Changes]
  - PAGI::Server (the module) no longer auto-configures Future::IO.
    pagi-server (the CLI) handles this automatically. Programmatic users
    of PAGI::Server must configure Future::IO themselves if using
    Future::IO-based libraries. See PAGI::Server "LOOP INTEROPERABILITY".

  [Improvements]
  - PAGI::SSE->every() now provides a clear error message if Future::IO
    backend is not configured, with example code showing how to fix it
  - New PAGI::Server documentation section "LOOP INTEROPERABILITY" explains
    Future::IO configuration for programmatic usage

0.001015 - 2026-01-18
  [New Features]
  - Add query parameter parsing to PAGI::WebSocket: query_params(), query_param(),
    raw_query_params(), raw_query_param() methods mirroring PAGI::Request API
  - Add form_param($name, %opts) and raw_form_param($name) singular accessors
    to PAGI::Request for fetching individual form values

  [Deprecations]
  - Rename PAGI::Request param methods for naming consistency (old names
    deprecated with warnings):
    - query() -> query_param()
    - raw_query() -> raw_query_param()
    - form() -> form_params()
    - raw_form() -> raw_form_params()

  [Improvements]
  - Improve PAGI::Test::Response json() error diagnostics: now shows HTTP
    status code, Content-Type header, and body preview when JSON parsing fails.
    Uses croak for better stack traces pointing to test file location.
  - Add $JSON_ERROR_BODY_LIMIT package variable to control body preview length
    in json() error messages (default 1500 bytes)


0.001014 - 2026-01-16
  [New Features]
  - Add Future::XS opt-in support for improved performance (~4% multi-worker)
    Use --future-xs flag or PAGI_FUTURE_XS=1 environment variable
  - path_param() now strict by default - dies with helpful message on missing
    keys, catching typos early. Use strict => 0 to opt out.

  [Bug Fixes]
  - Fix connection leak where connections weren't removed from tracking hash
    on EOF/disconnect, causing memory growth under sustained load
  - Fix POD reference to connection state specification (was pointing to
    non-existent file)

  [Improvements]
  - Simplify max_connections default to 1000 (removed unreliable auto-detection)
  - Lazy disconnect_future() allocation - Future only created when called,
    reducing per-request overhead for simple handlers
  - path_param() now delegates to path_params(), allowing subclasses to
    override path_params for custom parameter handling (e.g., lazy conversion
    from positional to named parameters)
  - Add _default_path_param_strict_opt() hook for subclasses to change default
  - Document query_params/query strict and raw options in POD

0.001013 - 2026-01-04
  [New Features]
  - Add PAGI::Utils::handle_lifespan utility for simplified lifespan handling
  - Add Response getters (status, headers, content_type, body) and try setters
  - Support static path rewrites in PAGI::App::File
  - Add full-demo example demonstrating lifespan, HTTP, WebSocket, and SSE

  [Bug Fixes]
  - Fix POD formatting of nested angle brackets
  - Make handle_lifespan croak on wrong scope type for clearer error messages

  [Improvements]
  - Match plackup FindBin behavior for app loading
  - Refactor PAGI::Response to use exists() for lazy defaults
  - Remove deprecated path_param/path_params from PAGI::Response
  - Document state method in PAGI::Request
  - Normalize spec docs to ASCII

  [Build/Distribution]
  - Add .editorconfig for consistent editor settings
  - Add Thunderhorse to implementations documentation

0.001012 - 2026-01-01
  [New Features]
  - PAGI 0.2 specification: Loop-agnostic apps, SSE POST support, keepalive events
  - Send-side backpressure to prevent unbounded memory growth under slow clients
  - Configurable write_high_watermark (64KB) and write_low_watermark (16KB)
    matching Python asyncio defaults for optimal memory/throughput balance
  - SSE disconnect reasons (client_closed, timeout, write_error, shutdown)

  [Bug Fixes]
  - Fix Lint middleware to preserve original app errors instead of masking them
  - Fix SSE keepalive to use chunked transfer encoding
  - Fix missing IO::Async::Timer::Periodic use statement
  - Fix missing 'method' field in SSE test scopes (per spec line 624)
  - Fix WebSocket scope incorrectly including 'method' field in test client
  - Fix middleware class resolution for nested namespaces (Auth::Basic, etc.)
    using ^ prefix for fully-qualified class names

  [Improvements]
  - Improved event loop portability (Future::IO now fully optional)
  - Improve max_connections auto-detection using getrlimit(RLIMIT_NOFILE)
    instead of sysconf(_SC_OPEN_MAX) for accuracy
  - Add fd_headroom config option (default 100) for max_connections calculation
  - Add TTY warning at startup when access_log goes to terminal
  - Security fixes from code review
  - PAGI spec compliance fixes for event validation

  [Build/Distribution]
  - Replace Markdown::Pod with App::sdview for spec POD generation
    - Fixes =item - to =item * (proper POD bullet points)
    - Converts module names to L<> links for metacpan

0.001011 - 2025-12-28
  - Switch license from Perl_5 to Artistic_2_0 (cleaner language, OSI recommended)
  - Fix Ctrl-C signal handling for multi-worker mode: lifespan.shutdown now runs
    for all workers ensuring proper cleanup
  - Add -e and -M flags to Runner for inline apps (like perl -e/-M)
  - Add .pl and .md to MIME types in PAGI::App::File
  - Add exception trapping to PAGI::Test::Client (default behavior)
  - Add SIGNAL HANDLING documentation to PAGI::Server POD
  - Add SECURITY.md and CONTRIBUTING.md to repository
  - Document async completion contract and detect missing responses
  - Cookbook improvements: JWT validation, Redis sessions, redirect behavior,
    WebSocket heartbeat example, IO::File::WithPath for filehandles

0.001010 - 2025-12-26
  - BREAKING: Removed sendfile() from PAGI::Server - use PAGI::Middleware::XSendfile
    with nginx/Apache X-Sendfile/X-Accel-Redirect instead for large file serving
  - Added handle_ranges option to PAGI::App::File for byte-range request support
  - Added FreeBSD CI testing
  - Documentation fixes for phantom module references

0.001009 - 2025-12-26
  - BREAKING: PAGI::Response constructor now takes ($scope, $send) instead of ($send, $scope)
    for consistency with Request, SSE, and WebSocket APIs
  - Added request_timeout for stalled requests, ws_idle_timeout and sse_idle_timeout
    for idle connection cleanup (all disabled by default for performance)
  - Test::Client: multi-value headers/query/form support, fixed arrayref headers with form/json
  - Scope-based caching for Request, Response, SSE, WebSocket objects
  - Replace retain() with adopt_future() for proper async error handling
  - Added on_error callback to heartbeat and rate limit middleware
  - Restructured Tutorial, added Cookbook for advanced patterns

0.001008 - 2025-12-25
  - Fixed 32-bit Perl compatibility in WebSocket test (pack 'Q' → 'NN')
  - Fixed sendfile on non-blocking sockets for FreeBSD/BSD systems
  - Added sendfile documentation and production recommendations
  - Improved test reliability: skip lsof/timing-dependent tests by default
  - Normalized test environment variables to use RELEASE_TESTING

0.001007 - 2025-12-24
  - More broken dist metadata fixes (part 2)

0.001006 - 2025-12-24
  - More broken dist metadata fixes

0.001005 - 2025-12-24
  - trying to get the indexer to ignore my test libs

0.001004 - 2025-12-24
  - More fixes to the code that generates the POD version of the specification
    in improve readability
  - exclude test and example libs from the CPAN indexer.
    
0.001003 - 2025-12-24
  - Fixes to the code that generates the POD version of the specification
    to try and fix UTF8 and other formatting issues.

0.001002 - 2025-12-24
  - Initial release. See README.md for a starting point

0.001001 - 2025-12-17
  - Placeholder release
