Skip to content

0.8.0

Choose a tag to compare

@somework somework released this 05 Sep 19:25
· 32 commits to main since this release

Added

  • Stable codes on every check line (spec 17 §5.1). Check\CheckItem carries ?string $code and ?string $host
    (appended, defaults null); CheckReport::ok()/warning()/error() take them as appended optional arguments, and
    CheckReport::add(), hasWarnings() and status() (the worst level) are new. Every line of Check\Checker,
    Check\DebounceStoreCheck (DebounceStoreCheck::CODE) and Adapter\OptionalPackage::check() (checkCode():
    <feature>.installed) names its check; Check\StaticCheck takes the code as an appended constructor argument. The
    codes are API and listed in docs/check-codes.md; the texts are not. Lines about one host
    carry it in $host.
  • Http\Response exposes the response headers: array<string, string> $headers = [] as the appended fourth
    constructor argument (names lower-cased; several values joined with , ), header(), contentType(),
    cacheMaxAge() (s-maxage, else max-age; no-store/no-cache are 0) and age(). Http\Psr18Transport::get(),
    post() and download() fill them; Testing\FakeTransport::download() passes them through. A custom transport
    that leaves them empty keeps working: check prints one neutral line instead of a verdict.
  • New check lines (spec 17 §5.1): key_file.content_type (error when the key file is served as something other
    than text/plain), key_file.cache_control (warning when Cache-Control/Age exceed key_file.cache_max_age),
    key_file.robots (warning when robots.txt disallows the key file path for every bot or an engine's bot;
    Checker::robotsDisallows() is public), key_file.previous (with previous_key: the old key file still served is
    the open rotation window, otherwise a warning) and the global http.client warning about redirects through a
    custom client. check now fetches /robots.txt (and the previous key file) of every host after its key file.
  • The 403 escalation counts across processes (spec 17 §5.3). Client::__construct(..., ?CacheInterface $failureCache = null, int $failureCacheTtl = Client::FAILURE_CACHE_TTL) (appended): with a PSR-16 cache the
    consecutive-403 counter and the "escalated" flag of each host live under <debounce.key_prefix>403.<host> (and
    …_escalated) with the TTL, so a fleet of PHP-FPM and queue workers writes the critical line once per streak; a
    cache with increment() counts atomically, any other approximately; a cache that throws is logged once and the
    process counts on. A non-403 answer deletes the counter only when it is set. Adapter\SubmitterFactory takes the
    cache as an appended argument, Adapter\ServicesBuilder::failureCache() / Services::failureCache() carry it,
    IndexNowKit::create(..., failureCache:) passes it. Check\Checker probes without it.
  • Submission\SubmissionStoreInterface (spec 17 §5.4): record(Result, DateTimeImmutable), recent(limit, host, status), lastFor(url); Submission\SubmissionRecord (urls, result, at) and Submission\NullSubmissionStore.
    Submitter::__construct(..., ?SubmissionStoreInterface $store = null, ?ClockInterface $clock = null) (appended)
    records one entry per Result after the listeners, skipped results included; a throwing store is one error log line
    and never affects delivery. Wired through Adapter\SubmitterFactory (appended $store),
    Adapter\ServicesBuilder::submissionStore() / Services::submissionStore() and IndexNowKit::create(..., submissionStore:). Implement tier; the "state → records" table is docs/submission-store.md.
    The indexnowkit/history package will ship the stores.
  • Canonical URLs (spec 17 §5.5): Url\CanonicalUrlNormalizer decorates a normalizer and strips tracking
    parameters (CanonicalUrlNormalizer::TRACKING_PARAMS, a growing constant: utm_*, gclid, dclid, wbraid,
    gbraid, fbclid, msclkid, yclid, ysclid, _openstat, etext, ttclid, twclid, igshid, mc_cid,
    mc_eid, mkt_tok, _hsenc, _hsmi, _ga), applies a trailing-slash policy and optionally sorts the query,
    before de-duplication and debounce. Options normalizer.strip_tracking_params (default true),
    normalizer.tracking_params, normalizer.trailing_slash (keep | add | strip), normalizer.sort_query
    (Config::OPTIONS, Config::TRAILING_SLASH_MODES). Url\UrlNormalizerFactory::fromConfig(Config) builds the
    normalizer everywhere the core used new UrlNormalizer(...) (Submitter, Client, IndexNowKit::create(),
    Adapter\Services, Check\Checker), so the options work in plain PHP too.
  • Attribute\Param\Condition and FieldCondition (spec 17 §5.6): when takes a string accessor, a Condition
    (evaluate(object): bool — Equals, or your own class) or a closure. A FieldCondition (field(),
    heldFor($oldValue)) lets ChangeClassifier read the old state from the ORM change set; a plain Condition is
    evaluated on the current object and cannot see an unpublish unless whenFields names the field (documented in
    attribute-reference.md). ParamExtractor::condition() takes string|Condition|Closure; UrlRule::accessorOf()
    reads a FieldCondition's field.
  • Config::toArray(): the effective configuration in the nested shape fromArray() takes, every option present with
    its resolved value (Config::fromArray($config->toArray()) is an equal configuration); keys are not masked, the
    config command does that.
  • Reason cases for the verify package (spec 17 §5.7; the enum is closed, so the core reserves them): Noindex,
    RobotsDisallowed, NonCanonical, Redirected (skips, not retryable) and OriginError (skip, retryable), with
    messages and translation keys; Reason::isRetryable() is new. The table of every case is in docs/bc.md. A match
    over Reason without a default arm fatals on them, as documented.

Changed

  • Tracking parameters are stripped by default (normalizer.strip_tracking_params: true): a URL with utm_*,
    gclid, fbclid, … is submitted and debounced as its clean form. The debounce keys of such URLs change once;
    URLs without those parameters are unaffected. Set normalizer.strip_tracking_params: false to keep the old
    behaviour.
  • Attribute\Param\Equals is a FieldCondition, not a ParamValue. params: ['x' => new Equals(...)] — which
    never produced a meaningful value — is now a ConfigurationException from ParamExtractor naming the fix (Equals
    belongs to when). when is typed string|Condition|Closure|null in IndexNow, IndexNowDefaults,
    IndexNowUrl, UrlRule, RuleCompiler, ParamExtractor::condition() and ChangeClassifier: a ParamValue
    (Accessor, Value, Formatted, Call) in when is a TypeError. Migration: when: new Accessor('x') →
    when: 'x'; anything else in when becomes a Condition.