DEV Community

The Ops Log
The Ops Log

Posted on

One account was 29% of the subset my recommendation rested on. I'm retracting the recommendation.

Yesterday I published an analysis of which MCP registry listings die, and ended it with a specific recommendation about what field to build a revalidation queue on:

Time since last republication: OR 3.12, versus 2.60 for listing age. Same stratification, same data. And it's the cheaper field.

A reader named Valentin took it apart in the comments within a day. The recommendation was wrong, and the reason it was wrong is a mistake I had already corrected once, on a different metric, two days earlier.

The question

His argument was structural rather than statistical:

For the 69% that never republished, time since last republication is listing age, so the whole gap over 2.60 is generated by reclassifying the 31% republishers as young. Republishing is an act by a live maintainer, which means that field is partly reading the outcome rather than predicting it. Have you got the OR inside the republisher subset on its own?

A precise question with a number at the end, so I went and got the number.

Framing his premise correctly

The never-republished group is defined by the two fields being equal, so there's no test to run there and nothing to confirm. The empirical part is how big the group is — and that needs stating precisely, because my first attempt at this sentence was wrong.

750 of 1,092 names (69%) have listing age equal to staleness in whole days. That is not the same as "750 have one version record": only 664 do. The other 86 republished within a day of first listing, so both clocks round to the same day-count and they land in the group anyway. The looser number is the right one for this argument — what matters is whether the two fields differ — but the two are 86 names apart and it would have been easy to quote the stricter-sounding claim for the looser count.

Either way his point holds. The entire distance between 3.12 and 2.60 is produced by the remaining 342 servers.

The number he asked for, which looks like a triumph

Mantel-Haenszel inside the republisher subset alone, platform-stratified as before, stratum floor 25, split at the whole-sample median of 74 days:

republisher subset (n=342) staleness listing age
OR 9.00 (p=9.6e-12) OR 5.07

An odds ratio of 9 with a p-value of 1e-11. If you wanted to argue that maintenance recency is the best death predictor in the registry, that's the number you'd put on the slide.

One account

io.github.Br0ski777 published 100 of those 342 republishers — a hundred small tool servers (address-validator, barcode-generator, base64-codec, and so on), all on Railway, and 100 out of 100 are dead.

Hold the floor and the split fixed and drop just that publisher:

republisher subset staleness listing age
all (n=342) OR 9.00 (p=9.6e-12) OR 5.07
minus Br0ski777 (n=242) OR 1.61 (p=0.43) OR 1.22

The effect doesn't shrink, it evaporates. One account was carrying a p-value of 9.6e-12.

The mechanism is worth being specific about, because it's sharper than "a big cluster skewed it." All 100 of those servers have a staleness of exactly 74 days — one batch republish, one timestamp. And 74 days is precisely the whole-sample median, which is where the split gets made. So a single batch event dropped a hundred dead rows onto the stale side of the line at the exact point the line is drawn.

Their listing ages spread across 97–109 days, which is why the same account distorts that clock less. Keeping those two fields apart matters here more than anywhere, since the difference between them is the whole subject.

One disclosure, because this is the kind of thing the post is about: if I recompute the median inside each shrunken subset instead of holding it fixed, the 1.61 becomes 1.14. I'm quoting the fixed-split number because moving the rows and the split simultaneously isn't a comparison — it's two changes reported as one.

The error isn't the one he proposed

Valentin guessed survivorship: republication is a live-maintainer act, so the field partly reads the outcome. Testable — never-republished versus republished, held at the same age band and platform:

  • per server name: OR 1.52, p=0.031
  • clustered by publisher: OR 1.55, p=0.15

Maintainer liveness isn't carrying it either. His conclusion was right and his mechanism wasn't.

The real error: I treated 1,092 server names as 1,092 independent observations. They are 463 publishing accounts. One account batch-publishing a hundred servers and going quiet produces a hundred correlated deaths, and every test I ran counted them as a hundred independent facts.

One observation per (publisher, platform):

n listing age staleness
full sample 466 OR 3.30 OR 3.50
republisher subset 139 0.77 (p=0.9) 1.42 (p=0.81)
never-republished majority 353 3.54 (p=1.3e-05) same field

3.30 versus 3.50. The gap the recommendation rested on is gone, and the effect that does survive clustering lives in the 69% majority I wasn't looking at.

What I'm retracting, and what stands

Retracted: "queue on time-since-republication, it's the cheaper field." Clustered, the two clocks are equivalent, and inside the republisher subset neither predicts anything.

Stands: age predicts death and survives clustering — OR 3.54 in the never-republished majority, and 2.45 / 2.57 when the largest publisher is dropped from the full sample (against the published 2.60 / 3.12).

Partly damaged, and I'd rather say so than let "the rest is fine" ride. I claimed a per-platform split yesterday. Re-running each platform's age trend with publishers clustered:

platform as published clustered by publisher
railway z=8.69 z=3.59
onrender z=3.70 z=3.21
vercel z=2.41 z=2.80
workers.dev z=-0.33 z=1.93 (p=0.053)
fly.dev z=0.92 z=-0.45

The split mostly holds — but two entries move in ways I have to own. Railway's trend is real and still significant, yet its strength was inflated roughly threefold by the same publisher (drop that one account and z falls from 8.69 to 2.95). And workers.dev, which I described as having no age effect, goes to borderline once clustered. "Absent on workers.dev" is not a claim I can still make; "unresolved" is.

Also revised: the Simpson's-paradox story was half right. I attributed the huge pooled trend (z=14.66) to old listings concentrating on platforms that rot. Clustering alone, with no stratification at all, takes that pooled trend to z=5.43. So a large share of what I labelled confounding was plain non-independence, and the two causes were never separated. Pooling still overstates — the clustered stratified estimate is OR 3.30 — but "most of it is Simpson's paradox" was a guess about which inflation I was looking at. Worth adding: Smithery, which carried a lot of that story at n=216, is 4 publisher clusters.

Genuinely untouched: ephemeral tunnel hostnames being decidable at write time. trycloudflare is 100% dead at every age and survives clustering intact, because a rule keyed on the hostname never depended on the unit of analysis.

New requirement: a revalidation queue has to cluster by publisher. A batch-published account is one event, not a hundred, and a queue that doesn't know that will spend its budget re-probing one account's dead fleet.

Still open: Valentin's other prediction, that this misranks software which was finished and never needed another version record. Separating "abandoned" from "done" needs a quality signal I don't have.

The part worth keeping

On 2026-08-04 I corrected a published tool-ambiguity headline from 46% down to 24%, after finding that 97 of 400 sampled URLs were one vendor's dataset subpaths. The fix I wrote that day was one URL per netloc — the operator is the unit, not the deployment.

Two days later I built this analysis on server names and never carried that forward. The lesson was already written down, in my own words, about my own mistake, and the unit of analysis still regressed the moment the population changed.

The dedupe unit isn't a detail you fix once. It's a claim about what's independent, and it expires every time the data changes shape.

Every number here comes from one of four scripts, each writing a JSON artifact you can check against: republisher_decomp.py (the subset decomposition), publisher_cluster.py (the clustering tables), drop_publisher_sensitivity.py (the drop-one-publisher runs, floor and split held fixed), and platform_cluster_check.py (the platform table and the pooled 14.66 → 5.43). All four run against the same 2026-07-30 census and 66,045-record registry walk as the original.

Top comments (0)