"We don't know" is a variant, not a number
Affects: lib/uptime-monitor.ts, lib/support-center.ts, lib/constants.ts, lib/security-posture.ts, config/site.ts, app/page.tsx, app/support/page.tsx, app/security/page.tsx, scripts/check-secret-modules.mjs, package.json, .env.example
What was decided
Availability is read from an external monitor at request time, and when no monitor has measured it the site publishes no figure at all. readUptime() returns a discriminated union whose unmeasured arm carries a reason and no number, so there is no value for a page to accidentally render.
What this replaced was worse than an approximation. PLATFORM_UPTIME_TRAILING_90 = 0.9995 sat in lib/constants.ts and was printed on three pages under the caption "Trailing 90 days, measured at the API edge in ca-central-1", alongside six hand-written per-component figures — 99.99%, 99.97%, 99.98% and so on, with exactly the variance that makes a number look observed. Nothing polled anything. The figures were chosen, and the sentence beside them described an instrument that did not exist. That is not a rounding error or an optimistic estimate; it is a specific factual claim about a measurement, placed where a buyer looks precisely because they want a measurement rather than a promise.
The constant is deleted rather than moved, with a comment in its place saying why and telling the next reader not to reintroduce one.
The distinction this rests on
An availability *commitment* and an availability *measurement* are different kinds of statement, and they now live in different modules. Commitments are AVAILABILITY_TIERS in lib/security-posture.ts — 99.9%, 99.95%, 99.99%, each with a service-credit schedule. Those are promises, we are entitled to make them, and nothing here changes them. Measurements come from lib/uptime-monitor.ts and exist only when something measured.
The failure mode was that the two were rendered in the same typeface, side by side, and the measurement was the fabricated one. A reader comparing "committed 99.95%" against "measured 99.95%" is being shown one number twice.
Four consequences that look like over-engineering and are not
The window travels with the figure, and is bounded by the youngest check. A monitor connected this morning has hours of history. windowLabel() cannot return "trailing 90 days" unless ninety days have elapsed. The first implementation of summarise() took the window from the *earliest* check, with a comment arguing that a check added yesterday should not shorten the window for one running since March. That was wrong, and wrong in a way that only appears when paired with the next decision: because platformAvailability is the minimum across components, the headline figure can come from the youngest check, and dating it from the oldest claims a month of evidence for two days of it. It now takes the latest start among contributing checks — the period over which every one of them has data. Adding a check today collapses the window to zero days, which is correct. This was caught by running the module against a stubbed fetch, not by reading it.
Platform availability is the minimum, not the mean. Five services at 100% and payments at 97% averages to 99.5%, which reads fine and describes nobody's experience. A customer who cannot invoice is down.
An unreachable monitor reports unmeasured, not stale. No last-known value is cached and re-rendered. The one moment this page is load-bearing is during an incident, and an incident is when a monitor is most likely to be unreachable — a green figure with no timestamp at that moment is the worst available output.
The board renders the monitor's own checks, not a mapping onto our six component names. What is watched is what is shown. A mapping layer is a place for a component to keep a green dot after the check behind it was deleted.
What is enforced mechanically
scripts/check-secret-modules.mjs runs in prebuild and in verify, and fails the build if any client component imports the monitor module, or if UPTIME_MONITOR_PROVIDER or UPTIME_MONITOR_API_KEY is read outside lib/uptime-monitor.ts.
(That script shipped with this decision as check-uptime-client.mjs, guarding this one module. D-014 added a second credentialled module with the same two rules, so the rules moved into a table and the script took the general name. The guarantee stated here is unchanged.)
The first rule exists because the browser failure is silent rather than loud: Next.js substitutes undefined for any non-NEXT_PUBLIC_ variable, so the module would report itself unconfigured, the board would read "not monitored" on a monitored platform, and nothing would be thrown or logged. The second exists because a second reader of the key is how a fallback gets introduced — a page infers from the key being present that the monitor is probably fine, and prints a number.
What is not yet true
No monitor account exists. The Better Stack and UptimeRobot readers are written from each vendor's published API shape and have never run against a real account, which is why they are strict: an unrecognised response returns unmeasured with the reason attached rather than reaching hopefully into an object and rendering NaN%, or a number taken from the wrong field. Both parsers, the configuration gaps, the summarising, the minimum-not-mean rule and the youngest-check window were exercised against a stubbed fetch before this was committed. The vendor-shape assumptions themselves cannot be verified until an account exists, and creating one requires a signup — the standing blocker class.
In production today, therefore, all three pages render the unmeasured branch. That is the intended state and not a regression: the site now says it does not publish a measured availability figure, which is true, where last week it published one that was false.
What would make this wrong
If a monitor is connected and its first window is embarrassing, the temptation will be to suppress the panel until the number improves. The union does not prevent that — a caller can always decline to render the observed arm. The commitment this decision encodes is narrower, and worth stating plainly: we publish what was observed together with the window it covers, or we say nothing was observed. We do not publish a third thing that looks like the first.