feat(plugin): explain a URL's render cadence, including the ladder rung; v0.77.0 - #182
Merged
Merged
Conversation
…ng; v0.77.0 `POST /prerender_admin/explain` exists to answer "why does this URL behave this way", and it could not answer it for the one property operators ask about most. It reported Target.renderInterval and stopped — but that is the CEILING, not the cadence. The value a row is actually scheduled from is resolveEffectiveInterval(url, target), which folds four inputs (route interval, stored interval, default, ladder rung) through two clamps (the route's demandFloor, the route's interval as a ceiling), and the answer is routinely none of the numbers an operator can see. Measured on a production cluster this week: a PDP route configured `renderInterval: 96h` was rendering every 48h. 95.7% of its targets carried a ladder rung, the route's `demandFloor: 48h` equalled the ladder's slowest rung, so `max(rung, floor)` clamped every rung to exactly 48h and the configured 96h ceiling never bound once. The knob named "floor" was the real cadence and the knob named "ceiling" was inert. Establishing that took fifteen explain calls plus reading Target.demandInterval out of the table by hand for 162 URLs and working the algebra backwards — because `demandInterval` was not in explain's select at all. So: select it, and add a `cadence` block reporting the whole chain — every input, which one supplied the base, and what clamped the result. `clampedBy` is the field to read first: 'floor' means the ladder wanted this page faster and the route refused (seeing it across a route means the ladder has no dynamic range there), 'ceiling' means a stored rung is slower than the route allows (what a rung outliving a lowered interval looks like), null means no rung. `explainCadence` lives in util/routeClass.js beside the resolver, not in the admin view that renders it, and derives from the same functions rather than recomputing the algebra. A second implementation would be a second thing to keep correct, and its failure mode is the worst available to a diagnostic: a view that confidently explains a cadence the scheduler is not using. A test cross-checks the two across the input matrix rather than trusting they stay in step. MERGE AFTER #181 (v0.76.0). Open PRs #165, #177 and #178 reserve v0.73.0, v0.74.0 and v0.75.0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
harper-joseph
force-pushed
the
feat/explain-cadence
branch
from
September 18, 2026 19:28
dc6487a to
36ba4f6
Compare
Contributor
Author
|
Renumbered to v0.77.0 (was 0.74.0). Open PRs #165, #177 and #178 already reserve v0.73.0, v0.74.0 and v0.75.0 respectively, and #181 now takes v0.76.0 — I bumped both of mine against 🤖 Generated with Claude Code |
…eachable floor `clampedBy` computed `floored` BEFORE the ceiling clamp and tested the floor first, so a route whose `demandFloor` is SLOWER than its `renderInterval` reported `clampedBy: 'floor'` while the answer came from the ceiling. Measured at route 24h with floor 48h: a 6h rung resolves to 24h, and the field the docs say to read first named a clamp that did not produce it — beside a reported 48h floor and a 24h cadence, three numbers an operator cannot reconcile. That is the failure this block exists to prevent: a view that confidently explains a cadence the scheduler is not using. The ceiling is tested first now. The existing cross-check matrix could not have caught it — every case used floor 48h under a 96h route, so `floor > base` was never exercised, and the matrix only asserts `effectiveInterval`, which correctly delegates to the resolver. Also warns at compile time when `demandFloor > renderInterval`, since that combination can never take effect — the route interval is clamped last — and it is precisely the misconfiguration someone opens `explain` to diagnose. Warned rather than corrected: which of the two numbers the author meant is not knowable from here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts: # package-lock.json # packages/plugin/package.json # packages/plugin/test/routeClass.test.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
POST /prerender_admin/explainexists to answer "why does this URL behave this way", and it could not answer it for the property operators ask about most.It reported
Target.renderIntervaland stopped. But that's the ceiling, not the cadence. The value a row is actually scheduled from isresolveEffectiveInterval(url, target), which folds four inputs — route interval, stored interval, default, ladder rung — through two clamps — the route'sdemandFloor, and the route's own interval as a ceiling. The answer is routinely none of the numbers an operator can see.What that cost, this week, on production
A PDP route configured
renderInterval: 96hwas rendering every 48h.95.7% of its targets carried a ladder rung; the route's
demandFloor: 48hequalled the ladder's slowest rung; somax(rung, floor)clamped every rung to exactly 48h and the configured 96h ceiling never bound once. The knob named "floor" was the real cadence and the knob named "ceiling" was inert.Establishing that took fifteen
explaincalls, plus readingTarget.demandIntervalout of the table by hand for 162 URLs and working the algebra backwards — becausedemandIntervalwasn't in explain'sselectat all. Along the way it produced two confidently wrong conclusions ("the ladder is inert", then "the ladder has no range because it isn't running"). Both were artifacts of not being able to see the rung.The change
demandIntervalto the targetselect. Without it the view cannot explain its own subject.cadenceblock reporting the whole chain:clampedByis the field to read first:floordemandFloorrefused. Seeing this across a route means the ladder has no dynamic range there at all.ceilingnullcadenceis null when there is no target: cadence is a property of a URL in the rotation, and reporting a route interval for a URL that owns no target would read as a schedule that doesn't exist.Where it lives, and why
explainCadenceis inutil/routeClass.jsbeside the resolver, not in the admin view that renders it, and it derives from the same functions rather than recomputing the algebra.A second implementation would be a second thing to keep correct, and its failure mode is the worst available to a diagnostic: a view that confidently explains a cadence the scheduler is not using. A test cross-checks
explainCadence().effectiveIntervalagainstresolveEffectiveInterval()across the input matrix rather than trusting the two stay in step.Fixed after adversarial review
clampedBycomputed the floored value before the ceiling clamp and tested the floor first, so a route whosedemandFlooris slower than itsrenderIntervalreported'floor'when the ceiling is what bound — a 48h floor, a 24h cadence, and the field this PR says to read first naming a clamp that did not produce the answer. Exactly the failure mode the section below warns about. The ceiling is tested first now.The cross-check matrix could not have caught it: every case used floor 48h under a 96h route, so
floor > basewas never exercised, and the matrix only assertseffectiveInterval(which correctly delegates to the resolver). Both gaps are now covered.Also added: a compile-time warning when
demandFloor > renderInterval, since that combination can never take effect and is precisely the misconfiguration someone opensexplainto diagnose.Tests
Four new cases in
test/routeClass.test.js, 1109 pass, lint and format clean. The first one pins the exact production shape above — floor equal to the slowest rung collapsing every graded page onto the floor — including the detail that a rung already at the floor reportsclampedBy: null, since nothing clamped it and saying otherwise would overstate what the floor is doing.Merge after #181 (v0.76.0). This is v0.77.0.
mainis 0.72.0, but four open PRs reserve the versions between: #165 → 0.73.0, #177 → 0.74.0, #178 → 0.75.0, #181 → 0.76.0. I originally bumped this to 0.74.0 having checked onlymainand the release list, which collided with #177.🤖 Generated with Claude Code