# This file documents the revision history for Perl extension PAGI

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


