Tags

Tags give the ability to mark specific points in history as being important
  • 1.3.0-beta5

    1.3.0-beta5
    
    Second cache-correctness fix from the 1.3.0-beta4 retest on
    portal-master. The amended canary protocol (admin + editor +
    anon) confirmed beta4's BigPipe suppression resolved scenario (a)
    but surfaced a second leakage pattern: with private-cache mode on
    the homepage cache keyed identically for anonymous and
    authenticated requests, so whichever variant populated the cache
    first was served to everyone. Root cause was on the cache-key
    side: the module emitted X-LiteSpeed-Cache-Control: private but
    did not tell LSWS to vary the cache entry on the session cookie.
    
    The reporter shipped a patch and a 27-request canary confirming
    the fix. beta5 lands the patch plus the obvious follow-ups.
    
    - Response subscriber auto-appends the session cookie name to the
      X-LiteSpeed-Vary header whenever private_cache.enabled = TRUE.
      Cookie name discovered via SessionConfiguration so the fix
      needs no new config keys.
    - Three new unit tests in LscacheResponseSubscriberTest cover the
      auto-append, the no-append path in public-only mode, and the
      deduplication path.
    - New status report row parses CacheVary cookie= directives out
      of .htaccess and warns when they don't match the emitted
      cookies, with a copy-pasteable suggested directive.
    - New docs section in docs/htaccess-gotchas.md on authenticated
      cache differentiation, with a drush ev one-liner to discover
      the session cookie name and an example IfModule LiteSpeed
      block.
    
    Pure cache-correctness fix; no API contract changes.
    
    CI: pipeline 821549 green (phpunit, phpcs, cspell, phpstan,
    eslint, composer-lint, composer all passing).
  • 1.3.0-beta4

    1.3.0-beta4: suppress LSCache caching when BigPipe placeholders are present
    
    Cache-correctness fix from the 1.3.0-beta3 retest on portal-master.
    The retest reporter corrected an earlier miscall on Finding 7
    (the auth-served-as-anonymous variant from the rc1 hand-back): the
    original canary protocol used authenticated-role-only test users
    with no visible per-user markup beyond what BigPipe streams, so the
    leak-detector saw identical responses regardless of whether the
    authenticated variant or the cached anonymous variant was served.
    The same operator hitting the canonical homepage as admin saw the
    anonymous-shape body; hitting a cache-busted URL produced the
    admin-shape body. Scenario (b) outcome: a real cache-correctness
    gap, not a downstream artifact of Finding 1.
    
    Root cause: BigPipe's Drupal core implementation resolves
    placeholders at streaming time inside PHP. LSWS serves cached
    responses without invoking PHP, so BigPipe never runs on cache
    hits and the placeholders stay unresolved. Authenticated content
    rendered via BigPipe (admin toolbar, user-account widgets,
    cached responses.
    
    What landed since 1.3.0-beta3 (commit 5593f00):
    
    - 86ed7f8: response subscriber scans the response body for
      `data-big-pipe-placeholder-id` markers. When detected, emits
      X-LiteSpeed-Cache-Control: no-cache to actively suppress
      LSWS-side caching. Active suppression is necessary because the
      HTTP Cache-Control is still public,max-age=N (the response is
      HTTP-cacheable in the abstract); a soft skip would let LSWS
      cache via its own header parsing anyway.
    
    - 39de4bc: three new unit tests on LscacheResponseSubscriberTest.
      testBigPipePlaceholderTriggersActiveNoCacheSuppression covers
      the suppression on the public branch. testBigPipePlaceholder
      SuppressesEvenInPrivateMode covers the private branch (BigPipe
      responses with user contexts that would otherwise qualify for
      private-cache emission still get suppressed because the private
      hit path also bypasses PHP). testNonBigPipeResponseEmitsPublic
      HeadersAsBefore is the regression guard verifying non-BigPipe
      responses continue to cache normally.
    
    - c5daa48: new docs section in htaccess-gotchas.md on the BigPipe
      interaction. Explains the streaming-vs-static incompatibility,
      documents the automatic suppression behaviour, and points
      operators with per-user content needs at the #type: 'lscache_esi'
      migration path with a before/after render-array example.
    
    - be1d4d9: ROADMAP records the beta4 cycle and the corrected
      Finding 7 attribution.
    
    Pure cache-correctness fix; no API contract changes. The
    HTTP 200 = SUCCEEDED mapping in the purger is unchanged.
    Operators who hit the beta3 BigPipe symptom can re-enable LSCache
    once on beta4 and re-run the amended canary protocol (admin role
    + editor role + anon, explicit checks for toolbar markup and
    BigPipe placeholder resolution).
    
    Deferred to 1.3.1 / 1.4.x (unchanged from beta3): post-purge
    sentinel verification flag, `everything` invalidation type, and
    the operator-experience polish items still in the queue.
    
    Soak target: clean re-test on the amended canary protocol from
    the env session; if clean, promotes straight to rc, then ~2 weeks
    soak, then 1.3.0 stable.
  • 1.3.0-beta3

    1.3.0-beta3: operator-experience fixes from the rc2 production-soak
    
    Stepping back from rc to beta after the 1.3.0-rc2 install reached
    the actual PURGE code path for the first time on a Cloudflare-
    fronted Jelastic-managed LSWS Enterprise environment and surfaced
    operator-experience gaps that warranted a fresh soak window before
    re-claiming rc quality.
    
    What landed since 1.3.0-rc2 (commit f037d9b):
    
    - 442e93f: actionable error messages. Suggested URL in the no-host
      failure reason changed from https://your-site.example.com to
      http://localhost/ (the previous example sent operators behind any
      CDN into a 400 trap). HTTP non-2xx purge failures now surface
      operator-actionable reasons via the new formatNon2xxReason()
      helper, detecting Cloudflare's HTTP 400 + CF-Ray signature
      specifically and pointing operators at origin-direct purge_host
      alternatives. Same change applied to the settings form
      description and placeholder.
    
    - 88d4398: purger logging now routes through @logger.channel.
      lscache_purger via constructor injection. The channel was
      registered in services.yml since 1.0.0 but never used; the plugin
      defaulted to PurgerBase's framework channel. drush watchdog:show
      --type=lscache_purger now returns purge-warning entries instead
      of "Unrecognized message type". Messenger-surfaced failure
      reasons now apply to every failure path (no-host, HTTP non-2xx,
      transport error), not just the no-host case rc2 covered.
    
    - 89a1150: two new doc sections in docs/htaccess-gotchas.md.
      "LSWS-side PURGE handling" makes explicit that HTTP 200 from
      LSWS means "request accepted at the protocol level," not "entry
      evicted." On managed-LSWS environments (Jelastic, Cloudways)
      and on Enterprise installs with admin-port routing, the two can
      be distinct states. Documents the diagnostic, LSWS configuration
      requirements, and operator workarounds. "LSCache vs Drupal's
      dynamic_page_cache UNCACHEABLE hint" covers the case where the
      two cacheability signals disagree, documents how to opt out via
      response policy when needed.
    
    - 5593f00: ROADMAP records the rc2 -> beta3 step-back with the
      rationale and the deferred-to-1.3.1/1.4.x list (post-purge
      sentinel verification flag, everything-invalidation type, the
      operator-experience polish items).
    
    Pure operator-experience + docs scope. No API contract changes;
    the "HTTP 200 = SUCCEEDED" mapping is unchanged. Finding 7 (auth-
    served-as-anon variant) was canary-clean on rc2 across 81
    controlled requests; treating that finding as resolved pending the
    invalidation-driven re-test once LSWS-side PURGE works in the test
    environment.
    
    Soak target ~5-7 days. If clean, promotes straight to rc3, then
    ~2-week soak, then 1.3.0 stable. If anything new surfaces, beta4
    cycle.
  • 1.3.0-rc2

    1.3.0-rc2: silent-failure fix on missing purger config
    
    Cut after the 1.3.0-rc1 production-soak handback from portal-master
    surfaced a silent failure mode: when lscache_purger.settings is
    missing from active config (likely from a partial config-sync
    round-trip on the operator side), purge_host reads as NULL, the
    host fallback chain in CLI/cron context collapses to empty, every
    tag invalidation bails with FAILED, and the only signal is a single
    watchdog warning that operators don't naturally check when drush
    just says FAILED.
    
    The bug has existed since 1.0.0-alpha2 (purger code unchanged
    through every release). The original prefix-mismatch hypothesis
    was ruled out by the env-session diagnostic showing identical
    TagHeaderBuilder::getPrefix() values on web and CLI.
    
    What landed since 1.3.0-rc1 (commit 676931f):
    
    - c0d7706: defensive install + backfill update hook on the
      lscache_purger submodule. ensure_settings_exist() runs in
      hook_install after the Purge plugin wiring; lscache_purger_
      update_8101() catches existing installs in the gap state via
      drush updb.
    
    - 72f613a: status report row for the purger surfaces three states
      (missing config, empty purge_host, OK). markAllFailed() also
      surfaces its failure reason via $this->messenger()->addError()
      so drush invocations show the cause inline instead of only in
      watchdog.
    
    - 1c53f72: README clarification that the lscache.settings:enabled
      toggle controls header emission only; LSWS-side caching is
      governed by the .htaccess CacheLookup directive separately. To
      fully bypass LSWS for a debugging window, comment out the
      directive rather than just toggling the module off.
    
    - 02047cf: regression-guard tests for the missing-host failure
      path. LscachePurgerTest covers the gap state (no config + no
      request stack + no $base_url) for both single and batch
      invalidations.
    
    - d65f454, 38a46cc, f037d9b: three CI fix-up commits addressing
      PHPStan, PHPCS, CSpell, and PHPUnit feedback (messenger via
      MessengerTrait, missing docblocks, banned-words swap, missed
      function spacing, plugin id placement).
    
    Strict bug-fix only relative to rc1; no API contract changes. The
    "everything" invalidation type that was discussed for this cycle
    was deferred to 1.3.1 or 1.4.x to preserve rc API freeze.
    
    Operator-experience polish from the rc1 hand-back report (status
    report directives summary, vary_cookies recent-traffic validation,
    demo ESI submodule + block-form ESI checkbox + drush list command)
    all carry over to the polish window after 1.3.0 stable.
    
    Finding 7 (auth-served-as-anon variant) from the rc1 hand-back is
    still outstanding; diagnostic ask queued to send to the env
    session alongside this rc2 deployment.
  • 1.3.0-rc1

    1.3.0-rc1: API freeze, pre-rc1 audit hardening landed
    
    Cut after a methodical pre-rc1 audit on the 1.3.x line. The audit
    read every PHP, YAML, and Markdown file on the branch, checked
    against Drupal coding standards, common security anti-patterns,
    and the SA-team-flag risk surface. One security finding (defence
    in depth on the ESI fragment route), two coding-standards items,
    two doc refreshes, three test-coverage gaps. All seven landed in
    the four "rc1 prep" commits since 1.3.0-beta2.
    
    The version bump signals API freeze for the 1.3.x track and asks
    operators to soak the module on production-shape installs ahead of
    1.3.0 stable. Per the 1.0.0 trajectory, rc1 -> ~2 weeks soak ->
    1.3.0 stable.
    
    What landed since 1.3.0-beta2 (commit 02e98fd):
    
    - B1 / 70c388e: TrustedCallbackInterface enforcement on ESI
      fragment callbacks. The fragment route previously gated callable
      invocation on HMAC token verification only; now also checks that
      the resolved class declares itself trusted via Drupal core's
      standard marker interface, mirroring the #lazy_builder policy.
      Defence in depth: bounds the impact of a hash-salt compromise.
      No exploitable bug; SA-team-aligned hardening.
    
    - S1+S2 / 4516f72: LscachePurgerSettingsForm constructor params
      now declare types (ConfigFactoryInterface, TypedConfigManager
      Interface). TagHeaderBuilder::resolvePrefix() removed entirely
      as it accepted a parameter it never used and just delegated to
      getPrefix().
    
    - S6+S7 / 480889f: lscache.install status report description
      updated to link at the 1.3.x docs branch instead of 1.0.x.
      htaccess-gotchas.md gained version notes for 1.0.0-rc1, 1.0.0,
      1.0.1, 1.1.x, 1.2.x, 1.3.x covering the additional .htaccess
      and LSWS-side directives each release introduces, plus a summary
      table mapping feature surface to required directives.
    
    - S3+S4+S5 / 676931f: three new unit-test classes covering
      Esi::preRenderEsi's missing-callback path, the fragment
      controller's TrustedCallbackInterface enforcement (positive and
      negative cases for both static-method and service-method
      callable shapes), and the install hook's flatten-defaults
      helper (six cases covering scalars, mappings, sequences, empty
      arrays, deep nesting, and mixed shapes).
    
    What 1.3.x cumulatively ships:
    
    - 1.0.x: cache-tag headers, Purge framework integration, status
      report .htaccess check, purge_host scheme validation
    - 1.1.x: per-user private cache mode driven by Drupal cache
      contexts, admin-route skip, log placeholder substitution
    - 1.2.x: ESI render element with HMAC-signed fragment URLs and
      TrustedCallbackInterface enforcement on the resolved callback
    - 1.3.x: named vary cookies that emit X-LiteSpeed-Vary header
      for every configured cookie on every cacheable response
    
    Stability commitment carried forward from beta2 unchanged: the
    config schema, response-header contract, render-element shape,
    fragment-route URL pattern, and TagHeaderBuilder service are all
    stable from this release. Path to 1.3.0 stable is bug-fix only.
  • 1.3.0-beta2

    1.3.0-beta2: form WSOD fix + config backfill hook + branch consolidation
    
    Two bug fixes from the 1.3.0-beta1 production-soak handback. The
    soak halted at Day 0 because neither bug let any traffic reach the
    install.
    
    Fix 1 (critical, form WSOD): the ConfigTarget closures for
    private_cache.contexts and vary_cookies declared strict array
    parameter types. Config::get() returns NULL when a key is absent;
    the strict typing threw a TypeError inside the closure and took
    out the entire settings page. Both closures now declare ?array and
    fall back to an empty list.
    
    Fix 2 (high, config backfill): new lscache_update_8103 walks the
    install YAML and writes any missing default keys to active config
    without overwriting operator changes. Generic implementation that
    backfills any missing key from config/install/lscache.settings.yml,
    so future schema additions pick up the same behaviour automatically.
    
    Branch consolidation: from 1.3.0-beta2 forward, all LSCache
    development happens on 1.3.x. The 1.1.x and 1.2.x branches are
    frozen at their respective beta1 tags. Operators previously on
    those should bump to 1.3.0-beta2 to pick up the fixes; 1.3.x's
    cumulative branch model means every prior feature is preserved.
  • 1.3.0-beta1

    1.3.0-beta1: API freeze for the 1.3.x vary-cookies track
    
    Cut after alpha3 came back from a clean re-test on portal-dev with
    both alpha2 bugs verified fixed:
    
    - ESI fragments render and invalidate via cache tags (alpha2 bug
      fixed by the Markup::create wrap forward-ported from 1.2.0-alpha3).
    - vary_cookies emits X-LiteSpeed-Vary: cookie=NAME on every cacheable
      response (alpha2 bug fixed by dropping the cache-context-required
      emission logic).
    - No regressions on inherited 1.1 admin-route + cache-poisoning
      checks.
    
    Same code as 1.3.0-alpha3 plus a README addition documenting the
    matching LSWS server-side CacheVary directive. The reporter caught
    this as a doc-level gap during the re-test - header was emitting
    correctly but LSWS didn't honour it without the server-side hint.
    
    What 1.3 ships: named vary-cookie support on top of 1.2.x's ESI
    work and 1.1.x's private cache. Operators configure cookie names
    in the settings form; the response subscriber emits the vary
    header for every configured cookie on every cacheable response.
    
    Cumulative: 1.3.0-beta1 contains all 1.0.x stable code + 1.1.x
    private cache + 1.2.x ESI + 1.3.x vary cookies. Operators wanting
    to validate the full 1.x line on one install can soak this build.
    
    Stability commitment: vary_cookies config schema, the
    X-LiteSpeed-Vary emission contract, and the matching CacheVary
    directive expectation are stable from this release. Path to 1.3.0
    stable is bug-fix only.
    
    Known issue carried forward, not blocking beta1: post-PURGE re-cache
    flake on responses without ESI markup, observed by the adopter
    during alpha3 re-test. Couldn't be isolated; flagged for further
    investigation.
  • 1.2.0-beta1

    1.2.0-beta1: API freeze for the 1.2.x ESI track
    
    Cut after alpha3 came back from a clean re-test on portal-dev with
    the alpha2 ESI bug verified fixed:
    
    - Token-tampering rejection clean across four variants (mutated
      args, mutated signature, swapped callback, garbage token).
    - Cache-tag invalidation refreshes ESI fragments correctly.
    - No regressions on inherited 1.1 admin-route + cache-poisoning
      checks.
    
    Same code as 1.2.0-alpha3 plus a ROADMAP docs bump. The version
    bump signals API freeze for the 1.2.x track and asks operators to
    soak the module on production-shape sites ahead of rc1.
    
    What 1.2 ships: an ESI render element ('lscache_esi') that emits
    <esi:include> tags for per-user fragments, plus the fragment route,
    HMAC-SHA256 token signer, and lscache_esi cache tag for site-wide
    invalidation. Builds on 1.1's private-cache machinery.
    
    Stability commitment: the 'lscache_esi' element shape, the
    fragment-route URL pattern, the token format, and the LscacheTokenSigner
    service are stable from this release. Path to 1.2.0 stable is bug-fix
    only.
  • 1.3.0-alpha3

    1.3.0-alpha3: ESI Markup wrap + vary-cookies always-emit-when-configured
    
    Two bug fixes from the 1.3.0-alpha2 adopter field test on portal-dev.
    
    Fix 1 (forward-port from 1.2.0-alpha3): ESI render element wraps its
    treats the emitted <esi:include> tag as already-trusted. Without the
    wrap, Xss::filterAdmin stripped the tag entirely and ESI fragments
    produced no output. Same wrap applied to the missing-callback fallback
    marker.
    
    Fix 2: vary-cookie emission no longer requires the response to declare
    a matching cookies:NAME cache context. Alpha2's logic intersected the
    configured cookie list with the response's cache contexts, which
    silently dropped the header on any response whose render code didn't
    happen to declare the context — meaning the feature was inert for
    most installs. Alpha3 emits X-LiteSpeed-Vary: cookie=A,B,C for every
    configured cookie on every cacheable response. LSWS handles cookies
    that are absent from a given request as "no variance" cleanly. The
    operator's config IS the directive.
    
    Existing vary-cookie unit tests updated to match the new always-emit
    behaviour. Empty-config default still emits no header, so installs
    that don't configure vary cookies see no behaviour change.
  • 1.2.0-alpha3

    1.2.0-alpha3: wrap ESI #markup with Markup::create to bypass XSS filter
    
    Critical bug fix from the 1.3.0-alpha2 adopter field test on
    portal-dev. ESI fragments produced no output at all in 1.2.0-alpha2
    because the renderer pipes #markup strings through Xss::filterAdmin(),
    which only whitelists a fixed set of HTML tags. <esi:include> is not
    on that list, so the entire string was being stripped to empty before
    output. Same outcome for the HTML-comment fallback marker.
    
    The fix is to wrap the emitted markup in \Drupal\Core\Render\Markup,
    which marks a string as already-trusted output and bypasses the
    filter. The src URL is separately escaped via htmlspecialchars before
    the wrap, so marking the resulting string as safe is correct.
  • 1.3.0-alpha2

    1.3.0-alpha2: forward-port of 1.1.0-alpha2 fixes onto vary-cookie baseline
    
    First public alpha of the 1.3.x track. Same vary-cookie response
    subscriber additions and config schema as the never-tagged
    1.3.0-alpha1, plus the two response-subscriber correctness fixes
    from 1.1.0-alpha2 brought forward via merge of 1.2.x (which itself
    merged 1.1.x):
    
    - Admin routes no longer emit any LSCache header.
    - Debug log messages contain resolved values inline.
    
    1.3.0-alpha1 was implemented and CI-validated but never tagged or
    released, since the alpha1 field-test loop ran on 1.1.x first.
    1.3.0-alpha2 is the first public alpha of the 1.3.x track.
  • 1.2.0-alpha2

    1.2.0-alpha2: forward-port of 1.1.0-alpha2 fixes onto ESI baseline
    
    First public alpha of the 1.2.x track. Same ESI render element and
    fragment-route work as the unreleased 1.2.0-alpha1, plus the two
    response-subscriber correctness fixes from 1.1.0-alpha2 brought
    forward via merge of 1.1.x:
    
    - Admin routes no longer emit any LSCache header (AdminContext-
      based skip, covers /admin/* plus contrib routes flagged
      _admin_route: TRUE).
    - Debug log messages contain resolved values rather than literal
      @placeholder text (inline sprintf substitution, works in dblog
      UI and drush watchdog:show alike).
    
    The 1.2.0-alpha1 tag was cut but never published as a release
    node, since the alpha1 field-test loop ran on 1.1.x first and
    surfaced bugs that needed to land before any 1.2 alpha hit
    operators.
  • 1.1.0-beta1

    1.1.0-beta1: API freeze for the 1.1.x track
    
    Cut after alpha2 came back from a clean re-test on portal-dev with
    both alpha1 findings verified fixed and zero regressions.
    
    Same code as 1.1.0-alpha2 (commit 1322537) plus a ROADMAP docs
    bump. The version bump signals API freeze for the 1.1.x track and
    asks operators to soak the module on production-shape installs
    ahead of rc1.
    
    What 1.1 ships: per-user private-cache mode for authenticated
    Drupal users. Responses whose Drupal cache contexts indicate
    per-user variation get X-LiteSpeed-Cache-Control: private,max-age=N
    automatically; operators do not hand-tag routes or fragments.
    
    Field-test history through alpha cycle:
    - Cache-poisoning canary clean across 18 trials.
    - Anonymous response back-compat byte-identical to 1.0.0-rc1.
    - Two bugs found and fixed in alpha2: admin-route over-emission,
      drush watchdog:show placeholder rendering.
    - Status report row dynamic against the .htaccess directive state.
    - Update hook lscache_update_8101 idempotent across repeats.
    
    Stability commitment: response-header shape, private_cache.* config
    schema, administer lscache permission, and TagHeaderBuilder service
    are stable from this release. Path to 1.1.0 stable is bug-fix only.
  • 1.1.0-alpha2

    1.1.0-alpha2: skip admin routes; substitute log placeholders inline
    
    Two bug fixes from the alpha1 field test on portal-dev. The cache-
    poisoning canary (3 cycles x 3 page shapes x 2 users = 18 trials)
    passed cleanly on alpha1, validating the architecture; what surfaced
    was scope-creep on the private-cache emission rather than a
    correctness flaw.
    
    Fix 1: admin routes no longer emit any LSCache header. Alpha1's
    decision tree only checked Cache-Control: no-store to disqualify
    responses, but admin pages return must-revalidate, no-cache, private
    (same as authenticated content) and so were being held in private
    cache for the full TTL. Practical impact was bounded by per-user
    keying (no cross-user leakage) but settings forms looked stale to
    operators for up to 600s after a change. Switched to AdminContext
    ::isAdminRoute() for the skip - covers everything under /admin/*
    plus contrib routes flagged _admin_route: TRUE.
    
    Fix 2: debug log messages now contain resolved values rather than
    literal @tags / @ttl placeholder text. Drupal's dblog UI substitutes
    the placeholders correctly, but drush watchdog:show doesn't run the
    same pipeline. Alpha1 used the deferred-substitution pattern; alpha2
    uses inline sprintf strings so every log consumer sees resolved
    values.
  • 1.2.0-alpha1

    1.2.0-alpha1: ESI render element on top of 1.1's private cache
    
    First alpha of the 1.2.x track. Adds a new render element type
    'lscache_esi' that emits an <esi:include src="..." /> tag pointing
    at a fragment route, plus the fragment route itself, plus an
    HMAC-SHA256 token signer that gates fragment access.
    
    Drupal-native: 'lscache_esi' accepts the same #callback and #args
    shape as #lazy_builder. A render-array author who already knows how
    to write a lazy builder can promote it to an ESI fragment with one
    key change. The surrounding response stays public-cacheable because
    the per-user variation is pushed out to the fragment, and the
    fragment itself emits private-cache headers picked up by 1.1's
    response subscriber.
    
    Token format is base64url(json-payload).hex(hmac-sha256), keyed on
    the site's hash salt. Tampering, replaying, or signing with a
    different salt all fail verification cleanly. Every fragment also
    carries an 'lscache_esi' cache tag so a single invalidation drains
    every fragment site-wide.
  • 1.1.0-alpha1

    1.1.0-alpha1: private-cache mode for authenticated users
    
    First alpha of the 1.1.x track. Extends caching from public-only to
    LSCache's per-user private cache. Authenticated user pages, which
    1.0.x passed through to PHP, can now be held per-user.
    
    Drupal-native: the response subscriber detects per-user cache
    contexts (user, user.permissions, user.roles, session, configurable)
    and emits X-LiteSpeed-Cache-Control: private,max-age=N
    automatically. Operators do not hand-tag routes or fragments.
    
    Off by default; private cache scales with active users. Default TTL
    600s (vs 3600s public default). Status report row warns if private
    mode is enabled but .htaccess lacks 'CacheLookup ... private on'.
    Sites upgrading from 1.0.x get the new defaults via
    lscache_update_8101.
    
    Roadmap order updated: 1.1 is now private cache, 1.2 is ESI. ESI
    fragments are private-cached responses at their own URLs, so
    private cache is the foundational primitive ESI builds on.
  • 1.0.1

    1.0.1 hardening release
    
    Adds element-level validation that restricts the LSCache Purger
    purge_host field to http:// and https:// schemes. Drupal's url
    element type otherwise accepts any scheme PHP's filter_var
    recognises (file, ftp, ldap, gopher, etc.). The HTTP purger only
    makes sense over http or https; other schemes would have failed at
    runtime anyway, but the new validator surfaces an immediate error
    and closes a (very narrow) avenue for an admin with the
    'administer lscache' permission to use the form to probe internal
    endpoints.
    
    Operator-facing only; permission-gated. No security advisory
    associated as no exploitable vulnerability was identified. Defence
    in depth from the 1.0.0 pre-stable security review.
  • 1.0.0

    1.0.0 stable
    
    First stable release of the Drupal.org-native LSCache integration.
    Cut after rc1 soaked cleanly across TubeSpanner Portal and several
    live production sites for ten days with no tag-invalidation
    regressions, header correctness issues, or open queue reports.
    
    Same code as rc1 plus a cosmetic info.yml description polish. 1.0.0
    signals API stability and opens the project to Drupal Security
    Advisory coverage. 1.0.x is now bug-fix and security-patch only;
    new feature work moves to 1.1.x.