@@ -78,7 +78,8 @@ slug ascending as deterministic tie-breakers; genuine gaps and duplicates are pr
7878` /podcast ` path is a moving latest-season representation selected from the maximum validated numeric
7979season, never a hard-coded season.
8080
81- The only accepted query is one exact ASCII ` season=[1-9][0-9]{0,8} ` value. An existing older season
81+ The only selector the catalogue reads is one exact ASCII ` season=[1-9][0-9]{0,8} ` value; every
82+ other parameter in the raw query rides along under the split recorded below. An existing older season
8283uses ` /podcast?season=N ` , an exact self-canonical, the title
8384` DataTalks.Club Podcast — Season N — DataTalks.Club ` , and sequence relations in descending catalogue
8485order: ` prev ` targets the adjacent newer actual season and ` next ` targets the adjacent older actual
@@ -92,12 +93,20 @@ in numeric descending order, visibly name each `Season N`, mark the current cont
9293` Newer season — Season 13 ` and ` Older season — Season 11 ` . A response never combines seasons or
9394invents a missing one.
9495
95- Duplicate, empty, signed, zero, leading-zero, encoded, non-ASCII, overlong, alternate-case, mixed,
96- unknown, former ` page ` , or otherwise non-exact query forms return bounded non-reflective ` 400 `
97- responses with ` no-store ` . An exact normalized positive season absent from the actual inventory
98- returns a bounded non-reflective ` 404 ` with ` no-store ` , without a latest/nearest fallback. ` GET ` and
99- ` HEAD ` have identical status and metadata behavior, with an empty HEAD body. Anonymous ` POST ` is
100- rejected before catalogue/query work with ` 405 ` , exact ` Allow: GET, HEAD ` , and ` no-store ` .
96+ Commit ` 643ea32 ` (2026-08-17, recorded in #196 ) split the raw query before the season grammar
97+ reads it, which deliberately amends the earlier whole-query refusal: the ` season ` selector is kept
98+ and handed to the grammar unchanged, and every other parameter — an unknown key, a campaign tag
99+ such as ` utm_source ` , the former ` page ` parameter, an alternate-case spelling, or a mixed query —
100+ is dropped, never reflected or forwarded, so a tagged URL such as ` /podcast?page=2 ` serves the
101+ byte-identical clean page declaring the clean canonical and never becomes an unbounded variant.
102+ The grammar stays exactly as strict about what it does read: duplicate, empty, signed, zero,
103+ leading-zero, encoded, non-ASCII, overlong, or otherwise non-exact ` season ` forms return bounded
104+ non-reflective ` 400 ` responses with ` no-store ` . The whole raw string stays length-bounded, so an
105+ enormous query is refused rather than parsed. An exact normalized positive season absent from the
106+ actual inventory returns a bounded non-reflective ` 404 ` with ` no-store ` , without a latest/nearest
107+ fallback. ` GET ` and ` HEAD ` have identical status and metadata behavior, with an empty HEAD body.
108+ Anonymous ` POST ` is rejected before catalogue/query work with ` 405 ` , exact ` Allow: GET, HEAD ` , and
109+ ` no-store ` .
101110
102111The ` .html ` and slash hub aliases preserve the raw query byte-for-byte in their one-hop redirects.
103112Season-query hub URLs stay out of the sitemap, whose podcast portion remains exactly clean
@@ -200,12 +209,18 @@ sitemap. Neither robots nor cache status is an authorization control.
200209## Query and poisoning rules
201210
202211- Static, detail, feed, and sitemap cache keys contain no query parameter. A named hub may allow
203- one exact registered positive-integer selector; Podcast allows only normalized ` season ` .
204- Duplicate, empty, overlong, out-of-range, malformed, former ` page ` , or unknown parameters become
205- no-store or a safe 400, never an unbounded variant.
212+ one exact registered positive-integer selector; Podcast allows only normalized ` season ` . Since
213+ ` 643ea32 ` (#196 ), a raw query is split before any grammar reads it: the parameters a hub actually
214+ selects on are kept — a duplicate, empty, overlong, out-of-range, or malformed selector still
215+ becomes no-store or a safe 400 — and every other parameter, including former ` page ` and unknown
216+ or tracking keys, is dropped, never reflected or forwarded, so a tagged URL collapses onto the
217+ clean canonical, never an unbounded variant. The whole raw string stays length-bounded.
206218- Known tracking keys may be removed by one safe canonical ` GET ` /` HEAD ` redirect. They are not
207- reflected or forwarded while absent from the key. Search text and arbitrary filters are not
208- cached in the MVP.
219+ reflected or forwarded while absent from the key. ` 643ea32 ` (#196 ) records why no allowlist of
220+ tracking parameters exists: every tool invents its own, so a list of the ones we happen to know
221+ would just move the breakage to the next vendor. Dropping every non-selector parameter is the
222+ allowlist-free form of the same not-reflected, never-a-variant intent. Search text and arbitrary
223+ filters are not cached in the MVP.
209224- Host, User-Agent, Referer, Accept-Language, CloudFront country, ` X-Forwarded-* ` , viewer-supplied
210225 internal headers, and arbitrary cookies are not public cache-key inputs. Accept-Encoding uses
211226 CloudFront's gzip/Brotli normalization rather than raw viewer values.
0 commit comments