The 0.73.0 → 0.77.0 train shipped. This issue now tracks the two items that outlived it, and records what the train actually did — because what shipped differs from the plan in one way that matters for the next bump.
What shipped (merged; deploy is a separate, still-pending human action — kohls-pr#116)
|
|
landed as |
| plugin #165 |
conditional sitemap fetching |
merged |
| plugin #177 |
new-target fast path |
merged |
| plugin #181 |
raw-document cache |
merged |
| plugin #182 |
explain cadence |
merged |
| kohls #108 |
sitemap anchor → 03:00 ET |
merged via kohls #114 |
| kohls #111 |
PDP departureAction, dry-run |
merged via kohls #114 |
| kohls #113 |
raw cache on catalog |
merged via kohls #114 |
The reserved versions collapsed. Only prerender-v0.77.0 was tagged — 0.73.0, 0.74.0, 0.75.0 and 0.76.0 exist as commits on main but never as releases, so the four PRs reached kohls in one tarball. main has since moved to 0.79.0 (0.78.0 raw-cache TTL, 0.79.0 readiness contracts).
Still open
1. #178 now bumps backwards
It sets packages/plugin to 0.75.0 against a main at 0.79.0, so merging it as-is is a downgrade of the kind that hit #59. It needs renumbering to the next free version — check gh release list at the time, don't reuse the number below.
2. #178's probe kill switch does not exist
It removes if (stats.aborted) triggers.stop() and states "Only disabling the probe clears it", but resetTriggerQueue's only caller is resetChangeProbeState, marked "Tests only", and syncProbeTimers never touches the queue. So with the probe disabled the queue keeps hard-expiring pages and filing renders for up to maxPending / ratePerSecond — ~4h37m at the new defaults. Verified in the diff. This is the blocker; the renumber is bookkeeping.
3. #178 also ships inert on kohls
A live override row holds changeProbe.maxTriggersPerSweep = 90000, and the override layer wins over both file and default. Merging changes nothing until that row is deleted — and deleting it later moves behaviour 90,000 → unlimited with no deploy and no PR. Its description needs an explicit deploy-then-delete step.
4. kohls #112 — the 81% figure is still unsourced
The threshold change rests on "a real mass reprice measures 81%", which came from the note on the live override row, not a measurement. Project notes from the same week record a promo-night reprice as 7.8–18.9% of products. If that is right, 0.4 sits above the event it is meant to catch and never fires. I could not settle it from the cluster: /prerender_admin/analytics ignores end, and a distant start hits the scanCap override, so historical per-pass probe data is not reachable. Needs whoever wrote the 81%. #112 stays draft until then.
The override-row dance, which is where an incident would come from
Still live, still order-dependent:
Read the live set before assuming anything: GET /prerender_admin/config. The repo does not describe the running system.
What to watch once the train is deployed (merged to kohls-pr main 2026-09-19; NOT yet deployed — the cluster runs a pre-0.73.0 plugin; see kohls-pr#116 for the rollout)
| from |
metric |
expectation |
| #165 |
prerender_ops sitemap_not_modified |
non-zero, or the feature is doing nothing |
| #177 |
sitemap_created_soon |
bounded by maxPerRun; watch the 15-min render burst |
| #113 |
raw_cache by outcome |
read the refusals; has-cookie climbing is the one worth an alert |
| #113 |
route_serve /catalog/ miss rate |
the number this exists to move (74–90% before) |
| #111 |
sitemap_departure_would_render / _reattached |
read together, see below |
Neither sitemap_not_modified nor sitemap_created_soon has a console surface — raw prerender_ops queries for now.
Before flipping sitemap.departure.dryRun: false
Rollback
- Plugin: revert the component to the previous tarball. A rename leaves the old component loaded, and
harper restart does not load staged components — use docker restart.
render.raw: enabled: false. Stored rows expire on their own (@table(expiration:)), nothing to clean up.
departureAction: sitemap.departure.enabled: false is the incident switch; it stops the behaviour without editing the route list.
- A canary bulk invalidation: delete the
Invalidation row.
Conflict traps, kept for the next train
Both were hit during the trial merge and both fail far from the conflict:
🤖 Generated with Claude Code
The 0.73.0 → 0.77.0 train shipped. This issue now tracks the two items that outlived it, and records what the train actually did — because what shipped differs from the plan in one way that matters for the next bump.
What shipped (merged; deploy is a separate, still-pending human action — kohls-pr#116)
departureAction, dry-runThe reserved versions collapsed. Only
prerender-v0.77.0was tagged — 0.73.0, 0.74.0, 0.75.0 and 0.76.0 exist as commits onmainbut never as releases, so the four PRs reached kohls in one tarball.mainhas since moved to 0.79.0 (0.78.0 raw-cache TTL, 0.79.0 readiness contracts).Still open
1. #178 now bumps backwards
It sets
packages/pluginto 0.75.0 against amainat 0.79.0, so merging it as-is is a downgrade of the kind that hit #59. It needs renumbering to the next free version — checkgh release listat the time, don't reuse the number below.2. #178's probe kill switch does not exist
It removes
if (stats.aborted) triggers.stop()and states "Only disabling the probe clears it", butresetTriggerQueue's only caller isresetChangeProbeState, marked "Tests only", andsyncProbeTimersnever touches the queue. So with the probe disabled the queue keeps hard-expiring pages and filing renders for up tomaxPending / ratePerSecond— ~4h37m at the new defaults. Verified in the diff. This is the blocker; the renumber is bookkeeping.3. #178 also ships inert on kohls
A live override row holds
changeProbe.maxTriggersPerSweep = 90000, and the override layer wins over both file and default. Merging changes nothing until that row is deleted — and deleting it later moves behaviour 90,000 → unlimited with no deploy and no PR. Its description needs an explicit deploy-then-delete step.4. kohls #112 — the 81% figure is still unsourced
The threshold change rests on "a real mass reprice measures 81%", which came from the note on the live override row, not a measurement. Project notes from the same week record a promo-night reprice as 7.8–18.9% of products. If that is right, 0.4 sits above the event it is meant to catch and never fires. I could not settle it from the cluster:
/prerender_admin/analyticsignoresend, and a distantstarthits thescanCapoverride, so historical per-pass probe data is not reachable. Needs whoever wrote the 81%. #112 stays draft until then.The override-row dance, which is where an incident would come from
Still live, still order-dependent:
changeProbe.canary.threshold= 0.7. Deploy feat(console): relative freshness, a miss taxonomy, and a bot filter on Traffic #112's file first (inert while the row exists), then delete the row. The reverse order leaves the threshold at the package default of 0.1 in between — the value behind the 2026-09-10 false trip and ~18h of degraded cache.changeProbe.maxTriggersPerSweep= 90000. Deleting it is what activates Queue every detected change instead of deferring past a budget (v0.75.0) #178. Do it deliberately, not as cleanup.Read the live set before assuming anything:
GET /prerender_admin/config. The repo does not describe the running system.What to watch once the train is deployed (merged to kohls-pr main 2026-09-19; NOT yet deployed — the cluster runs a pre-0.73.0 plugin; see kohls-pr#116 for the rollout)
prerender_opssitemap_not_modifiedsitemap_created_soonmaxPerRun; watch the 15-min render burstraw_cacheby outcomehas-cookieclimbing is the one worth an alertroute_serve/catalog/miss ratesitemap_departure_would_render/_reattachedNeither
sitemap_not_modifiednorsitemap_created_soonhas a console surface — rawprerender_opsqueries for now.Before flipping
sitemap.departure.dryRun: falsesitemap_not_modifiedconfirmed non-zero. Conditional sitemap fetching — skip the reconcile on a 304 (v0.73.0) #165 converts re-attributions that would have come from a 304'd child intowould_render, sodeparture_reattachedimproves because of Conditional sitemap fetching — skip the reconcile on a 304 (v0.73.0) #165, not because the corpus stopped shearing.departed→ one wasted hard-expiry and render for a product that never left.maxActionsoverflow is dropped permanently, whatever the schema doc says:Sitemap.js:601nullssitemapUrlbefore:603records the candidate, and the feeding scan queriessitemapUrl == <child>. At ~4,173 removals/day against the 5000 default we sit at 83% of the cap.Rollback
harper restartdoes not load staged components — usedocker restart.render.raw:enabled: false. Stored rows expire on their own (@table(expiration:)), nothing to clean up.departureAction:sitemap.departure.enabled: falseis the incident switch; it stops the behaviour without editing the route list.Invalidationrow.Conflict traps, kept for the next train
Both were hit during the trial merge and both fail far from the conflict:
configSchema.js, Conditional sitemap fetching — skip the reconcile on a 304 (v0.73.0) #165 × Render a newly declared sitemap URL soon, not a full interval later (v0.74.0) #177. A union resolve silently drops the closing brace of Conditional sitemap fetching — skip the reconcile on a 304 (v0.73.0) #165'sconditionalgroup and the file fails to parse ~500 lines later. Keep both groups and closeconditionalexplicitly.test/routeClass.test.js, feat(plugin): cache origin documents for URLs outside the render rotation; v0.76.0 #181 × feat(plugin): explain a URL's render cadence, including the ladder rung; v0.77.0 #182. Both append; feat(plugin): explain a URL's render cadence, including the ladder rung; v0.77.0 #182 also edits the import block. Take feat(plugin): explain a URL's render cadence, including the ladder rung; v0.77.0 #182's file as the trunk and append feat(plugin): cache origin documents for URLs outside the render rotation; v0.76.0 #181's block.METRICS.mdrows in a Conditional sitemap fetching — skip the reconcile on a 304 (v0.73.0) #165 × Render a newly declared sitemap URL soon, not a full interval later (v0.74.0) #177-shaped resolution.metrics.sitemapRuntakes a free-form series string, sotest/metrics.test.jscannot detect a dropped row — a one-sided resolution ships a series nobody can read, which is theDYNAMIC_SERIES_SLOTshape.🤖 Generated with Claude Code