Skip to content

feat(plugin): explain a URL's render cadence, including the ladder rung; v0.77.0 - #182

Merged
harper-joseph merged 3 commits into
mainfrom
feat/explain-cadence
Sep 18, 2026
Merged

harper-joseph merged 3 commits into
mainfrom
feat/explain-cadence

Conversation

@harper-joseph

@harper-joseph harper-joseph commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

The gap

POST /prerender_admin/explain exists 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.renderInterval and stopped. But that's 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, 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: 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 wasn't in explain's select at 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

  • Add demandInterval to the target select. Without it the view cannot explain its own subject.
  • Add a cadence block reporting the whole chain:
"cadence": {
  "effectiveInterval": 172800000,   // what nextRenderTime is computed from
  "baseFrom": "route",              // route | stored | default
  "baseInterval": 345600000,
  "routeInterval": 345600000,
  "storedInterval": 86400000,
  "defaultInterval": 86400000,
  "demandInterval": 21600000,       // the ladder's stored rung
  "demandFloor": 172800000,
  "clampedBy": "floor"
}

clampedBy is the field to read first:

value meaning
floor the ladder wanted this page faster and the route's demandFloor refused. Seeing this across a route means the ladder has no dynamic range there at all.
ceiling the stored rung is slower than the route allows — what a rung outliving a lowered route interval looks like
null nothing clamped: either no rung, or a rung that is already at the floor and within the ceiling

cadence is 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

explainCadence is in util/routeClass.js beside 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().effectiveInterval against resolveEffectiveInterval() across the input matrix rather than trusting the two stay in step.

Fixed after adversarial review

clampedBy computed the floored value before the ceiling clamp and tested the floor first, so a route whose demandFloor is slower than its renderInterval reported '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 > base was never exercised, and the matrix only asserts effectiveInterval (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 opens explain to 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 reports clampedBy: null, since nothing clamped it and saying otherwise would overstate what the floor is doing.

⚠️ Merge order

Merge after #181 (v0.76.0). This is v0.77.0.

main is 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 only main and the release list, which collided with #177.

🤖 Generated with Claude Code

…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 harper-joseph changed the title feat(plugin): explain a URL's render cadence, including the ladder rung; v0.74.0 feat(plugin): explain a URL's render cadence, including the ladder rung; v0.77.0 Sep 18, 2026
@harper-joseph

Copy link
Copy Markdown
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 main and the release list without checking open PRs first, which is exactly the collision the contributing notes warn about. Merge order: #181 then this.

🤖 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant