CodeIssuesPull RequestsActionsSecurityInsights
✨ AI
More
Settings

feat(status): REPORTING_EPOCH — launch day becomes a one-env-var event #5553

MergedXSccantynz wants to mergefeat/reporting-epochmainopened 6d ago
3 changed files+134−18
Modified.env.example+8−0View fileUnifiedSplit
8484# openssl rand -hex 32, and set it identically as the GitHub repo secret.
8585# Unset → the endpoint refuses every call (401); it never fails open.
8686HEARTBEAT_REPORT_TOKEN=
87
88# The date /status measures from (ISO, e.g. 2026-09-15). Set at
89# customer-launch for a clean reporting era: uptime windows clamp to it,
90# fully-pre-epoch incidents leave the figures and history, days-since
91# caps at its age, and the page prints the date. Unset = the incident
92# ledger's birth date (2026-08-09) — figures never claim time nobody was
93# recording.
94REPORTING_EPOCH=
8795ANTHROPIC_API_KEY=
8896# Email (Block A8). Provider=log just writes to stderr (safe default).
8997# Switch to "resend" in prod and set RESEND_API_KEY.
Modifiedsrc/__tests__/status-uptime.test.ts+46−0View fileUnifiedSplit
125125 expect(UPTIME_WINDOW_MS).toBe(24 * 60 * 60 * 1000);
126126 });
127127});
128
129describe("reporting epoch (owner policy 2026-08-27: figures measure from a declared clean start)", () => {
130 it("effectiveWindowMs clamps a window to the time since the epoch", async () => {
131 const { effectiveWindowMs } = await import("../routes/status");
132 const now = Date.parse("2026-08-27T00:00:00Z");
133 const epoch = Date.parse("2026-08-09T00:00:00Z"); // 18 days earlier
134 const ninetyDays = 90 * 24 * 60 * 60 * 1000;
135 expect(effectiveWindowMs(ninetyDays, now, epoch)).toBe(
136 18 * 24 * 60 * 60 * 1000
137 );
138 // A window shorter than the recorded era passes through untouched.
139 expect(effectiveWindowMs(UPTIME_WINDOW_MS, now, epoch)).toBe(
140 UPTIME_WINDOW_MS
141 );
142 });
143
144 it("effectiveWindowMs floors at one hour — a just-set epoch cannot zero the denominator", async () => {
145 const { effectiveWindowMs } = await import("../routes/status");
146 const now = Date.parse("2026-08-27T00:00:00Z");
147 expect(effectiveWindowMs(UPTIME_WINDOW_MS, now, now)).toBe(
148 60 * 60 * 1000
149 );
150 // Epoch in the future (misconfiguration) also floors, never negative.
151 expect(
152 effectiveWindowMs(UPTIME_WINDOW_MS, now, now + 999999)
153 ).toBe(60 * 60 * 1000);
154 });
155
156 it("reportingEpochMs falls back to the ledger birth date on unset or garbage", async () => {
157 const { reportingEpochMs, INCIDENT_LEDGER_EPOCH_MS } = await import(
158 "../routes/status"
159 );
160 const prev = process.env.REPORTING_EPOCH;
161 try {
162 delete process.env.REPORTING_EPOCH;
163 expect(reportingEpochMs()).toBe(INCIDENT_LEDGER_EPOCH_MS);
164 process.env.REPORTING_EPOCH = "not-a-date";
165 expect(reportingEpochMs()).toBe(INCIDENT_LEDGER_EPOCH_MS);
166 process.env.REPORTING_EPOCH = "2026-09-15";
167 expect(reportingEpochMs()).toBe(Date.parse("2026-09-15"));
168 } finally {
169 if (prev === undefined) delete process.env.REPORTING_EPOCH;
170 else process.env.REPORTING_EPOCH = prev;
171 }
172 });
173});
Modifiedsrc/routes/status.tsx+80−18View fileUnifiedSplit
6767 */
6868export const INCIDENT_LEDGER_EPOCH_MS = Date.parse("2026-08-09T00:00:00Z");
6969
70/**
71 * The reporting epoch — the date this page's figures measure FROM.
72 *
73 * Owner policy (2026-08-27): "we will start the accurate honest reporting
74 * once we are customer ready." REPORTING_EPOCH (ISO date, e.g.
75 * 2026-09-15) is the one switch that implements it: on launch day, set it
76 * and every uptime window, the days-since figure, and the incident
77 * history measure from that date — a clean start with no build-era noise,
78 * honestly labeled with the date itself. Until it is set, the ledger's
79 * own birth date applies, so no figure ever claims time nobody was
80 * recording. An unparseable value falls back to the ledger epoch rather
81 * than silently measuring from 1970.
82 */
83export function reportingEpochMs(): number {
84 const raw = (process.env.REPORTING_EPOCH || "").trim();
85 if (raw) {
86 const parsed = Date.parse(raw);
87 if (Number.isFinite(parsed)) return parsed;
88 }
89 return INCIDENT_LEDGER_EPOCH_MS;
90}
91
92/**
93 * A window may not reach back past the reporting epoch: "90d uptime" over
94 * 17 recorded days is 100%-of-17-days, not a claim about the other 73.
95 * Floored at one hour so a just-set epoch cannot produce a zero-width
96 * window (division by ~0 → nonsense percentages).
97 */
98export function effectiveWindowMs(
99 windowMs: number,
100 now: number,
101 epochMs: number = reportingEpochMs()
102): number {
103 const sinceEpoch = now - epochMs;
104 return Math.max(60 * 60 * 1000, Math.min(windowMs, sinceEpoch));
105}
106
70107/**
71108 * 24h uptime percentage, derived from recorded incidents.
72109 *
482519 // from OUTSIDE the box (src/routes/heartbeat-report.ts), so a dead server
483520 // still shows up as downtime here rather than as a blank the page cannot
484521 // see.
485 const aggregatePct = uptimePctFromIncidents(incidentWindow, nowMs);
522 // Every figure measures from the reporting epoch (REPORTING_EPOCH, or
523 // the ledger's birth date until the owner sets it at customer-launch):
524 // windows clamp so "90d uptime" is uptime over the RECORDED slice of
525 // those 90 days, incidents that fully predate the epoch leave the math
526 // and the history table, and days-since caps at the epoch's age. Owner
527 // policy 2026-08-27: accurate honest reporting starts at customer-ready
528 // — one env var flips the whole page onto the clean era.
529 const epochMs = reportingEpochMs();
530 incidentWindow = incidentWindow.filter((i) => {
531 if (!i.resolvedAt) return true; // open incidents always count
532 const end = new Date(i.resolvedAt).getTime();
533 return !Number.isFinite(end) || end >= epochMs;
534 });
535 const aggregatePct = uptimePctFromIncidents(
536 incidentWindow,
537 nowMs,
538 effectiveWindowMs(UPTIME_WINDOW_MS, nowMs, epochMs)
539 );
486540 const aggregateStr = fmtUptimePct(aggregatePct);
487541 const uptime30dStr = fmtUptimePct(
488 uptimePctFromIncidents(incidentWindow, nowMs, UPTIME_WINDOWS["30d"])
542 uptimePctFromIncidents(
543 incidentWindow,
544 nowMs,
545 effectiveWindowMs(UPTIME_WINDOWS["30d"], nowMs, epochMs)
546 )
489547 );
490548 const uptime90dStr = fmtUptimePct(
491 uptimePctFromIncidents(incidentWindow, nowMs, UPTIME_WINDOWS["90d"])
549 uptimePctFromIncidents(
550 incidentWindow,
551 nowMs,
552 effectiveWindowMs(UPTIME_WINDOWS["90d"], nowMs, epochMs)
553 )
492554 );
493555 const daysSince = daysSinceLastIncident(incidentWindow, nowMs);
494 // "None found in the window" used to render "90+" — but the incident
495 // ledger has only EXISTED since the reliability build shipped the
496 // recorder (2026-08-09). Ninety incident-free days cannot be claimed
497 // from seventeen days of records; the truthful cap is the ledger's own
498 // age (owner directive 2026-08-27: complete honesty in reporting —
499 // the truth always wins).
500 const ledgerAgeDays = Math.max(
501 0,
502 Math.floor((nowMs - INCIDENT_LEDGER_EPOCH_MS) / DAY_MS)
503 );
556 // "None found in the window" used to render "90+" — but ninety
557 // incident-free days cannot be claimed from fewer days of records; the
558 // truthful cap is the reporting era's own age.
559 const epochAgeDays = Math.max(0, Math.floor((nowMs - epochMs) / DAY_MS));
504560 const daysSinceStr =
505561 daysSince === null
506 ? String(Math.min(ledgerAgeDays, Math.floor(UPTIME_LOOKBACK_MS / DAY_MS)))
507 : String(Math.min(daysSince, ledgerAgeDays));
562 ? String(Math.min(epochAgeDays, Math.floor(UPTIME_LOOKBACK_MS / DAY_MS)))
563 : String(Math.min(daysSince, epochAgeDays));
564 const epochDateStr = new Date(epochMs).toLocaleDateString("en-US", {
565 month: "short",
566 day: "numeric",
567 year: "numeric",
568 timeZone: "UTC",
569 });
508570
509571 // Per-service status descriptors — kept here so the JSX is just markup.
510572 //
707769 <p class="status-hero-method">
708770 How uptime is measured: computed from recorded incidents —
709771 rolling 24h / 30-day / 90-day windows, overlapping incidents
710 merged, an open incident counted up to now. Incident
711 recording began Aug 9, 2026; windows reaching further back
712 cover only the recorded period. Incidents are filed by the
772 merged, an open incident counted up to now. Figures measure
773 from {epochDateStr}; windows reaching further back cover
774 only the period since then. Incidents are filed by the
713775 in-process synthetic monitor
714776 {externalWriterArmed ? (
715777 <>
716778
c comment · e edit title · m merge · a approve · r request changes · ? shortcuts