You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.