Skip to content
390 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas1/**
2 * The status page at status.g1t.sh (apps/status): what its JSON and feeds
3 * say, and the staff-only entrypoint sudo runs incidents, maintenance and
4 * postmortems through. Fields are snake_case, like the API.
5 */
6import type { Result } from "./result";
7
8/**
9 * One part's state. Its last check, made worse by an open incident's
10 * impact on it, or `maintenance` during a maintenance window.
11 * `unmonitored`: no check exists for it and nothing is reported.
12 */
13export type StatusComponentState = "up" | "degraded" | "partial" | "down" | "maintenance" | "unmonitored";
14
15/** The whole of g1t, in one word. `unknown` until anything has been checked. */
16export type StatusOverallState = "up" | "degraded" | "down" | "maintenance" | "unknown";
17
18/** Where an incident stands, in the words status pages use. */
19export type IncidentStatus = "investigating" | "identified" | "monitoring" | "resolved";
20
21/** How bad an incident is, for the team: SEV1 is the worst. Never shown on the status page. */
22export type IncidentSeverity = "sev1" | "sev2" | "sev3" | "sev4";
23
24/** What an incident does to one part, while it is open. */
25export type ComponentImpact = "operational" | "degraded" | "partial_outage" | "major_outage";
26
27/** An incident's impact as a whole: some parts having trouble, or a major outage. */
28export type IncidentImpact = "degraded" | "down";
29
30export type StatusComponent = {
31 key: string;
32 name: string;
33 /** Where people meet it: `api.g1t.sh`. */
34 address: string;
35 /** What the check looks at, said plainly. */
36 checks: string;
37 /** What the page shows: the check, an incident's impact (the worse wins), or maintenance. */
38 state: StatusComponentState;
39 /** What the last check alone said. */
40 check_state: StatusComponentState;
41 /** What was seen: "Answered in 84 ms", "Failed: HTTP 502". */
42 detail: string;
43 latency_ms: number | null;
44 /** Share of checks that answered over the last 90 days, 0 to 100. Null with no checks yet. */
45 uptime_90d: number | null;
46};
47
48export type StatusIncidentUpdate = {
49 id: string;
50 at: string;
51 status: IncidentStatus;
52 text: string;
53};
54
55export type StatusIncident = {
56 id: string;
57 title: string;
58 /** `down` when any part has a major outage. */
59 impact: IncidentImpact;
60 status: IncidentStatus;
61 /** Keys of the parts it affects. */
62 components: string[];
63 /** What it does to each part. */
64 component_impacts: { key: string; impact: ComponentImpact }[];
65 started_at: string;
66 /** Null while it is still going on. */
67 resolved_at: string | null;
68 /** Its page: `https://status.g1t.sh/incidents/<id>`. */
69 url: string;
70 /** When its postmortem was published on that page; null until then. */
71 postmortem_published_at: string | null;
72 /** Newest first. */
73 updates: StatusIncidentUpdate[];
74};
75
76export type MaintenanceState = "scheduled" | "in_progress" | "completed" | "cancelled";
77
78export type StatusMaintenanceUpdate = { id: string; at: string; text: string };
79
80/** Planned work, announced ahead. Its parts show "Under maintenance" during the window. */
81export type StatusMaintenance = {
82 id: string;
83 title: string;
84 message: string;
85 components: string[];
86 /** The window, RFC 3339, UTC. */
87 starts_at: string;
88 ends_at: string;
89 state: MaintenanceState;
90 url: string;
91 /** Newest first. */
92 updates: StatusMaintenanceUpdate[];
93};
94
95export type StatusOverall = {
96 state: StatusOverallState;
97 /** Two or three words: "All systems normal", "Partial outage", "Major outage". */
98 title: string;
99 /** One sentence, naming what is affected. */
100 line: string;
101};
102
103/** `GET status.g1t.sh/status.json`. */
104export type StatusReport = {
105 /** When the parts were last checked; null before the first check. */
106 checked_at: string | null;
107 overall: StatusOverall;
108 components: StatusComponent[];
109 /** Open incidents, and those resolved in the last 90 days, newest first. */
110 incidents: StatusIncident[];
111 /** Maintenance under way or still to come, soonest first. */
112 maintenance: StatusMaintenance[];
113};
114
115// --- Staff ---------------------------------------------------------------------
116
117/** `draft`: detected or saved, not on the page yet. `dismissed`: a draft closed as a false alarm. */
118export type IncidentVisibility = "draft" | "public" | "dismissed";
119
120/** What a line on an incident's timeline records. */
121export type TimelineKind =
122 | "declared"
123 | "detected"
124 | "published"
125 | "dismissed"
126 /** A public update: what the status page shows. */
127 | "update"
128 /** A note only staff see. */
129 | "note"
130 | "status"
131 | "severity"
132 | "impact"
133 | "role"
134 | "acknowledged"
135 /** A check flipped back to working while the incident was open. */
136 | "recovered"
137 /** A check kept failing while the incident was open. */
138 | "failing"
139 | "followup"
140 | "postmortem";
141
142export type TimelineEntry = {
143 id: string;
144 at: string;
145 /** A staff email, or `status` for what the checks wrote. */
146 by: string;
147 kind: TimelineKind;
148 /** Shown on the status page. */
149 public: boolean;
150 /** For public updates: the status it was posted with. */
151 status: IncidentStatus | null;
152 text: string;
153 /** How many subscribers it was emailed to; null when it was not. */
154 notified: number | null;
155};
156
157export type FollowUp = {
158 id: string;
159 title: string;
160 owner: string | null;
161 done_at: string | null;
162 created_at: string;
163 created_by: string;
164};
165
166export type PostmortemFields = {
167 summary: string;
168 impact: string;
169 timeline: string;
170 root_cause: string;
171 went_well: string;
172 went_badly: string;
173 /** What will change, one per line; filled in from the follow-ups. */
174 action_items: string;
175};
176
177export type Postmortem = PostmortemFields & {
178 updated_at: string;
179 updated_by: string;
180 published_at: string | null;
181};
182
183/** Seconds from the impact starting to each milestone, once reached. */
184export type IncidentDurations = {
185 to_acknowledge: number | null;
186 to_mitigate: number | null;
187 to_resolve: number | null;
188};
189
190export type AdminIncident = {
191 id: string;
192 title: string;
193 severity: IncidentSeverity;
194 status: IncidentStatus;
195 visibility: IncidentVisibility;
196 /** Declared by staff, or drafted when a check kept failing. */
197 source: "declared" | "detected";
198 components: { key: string; impact: ComponentImpact }[];
199 /** When the impact began (can be earlier than declared). */
200 started_at: string;
201 declared_at: string;
202 acknowledged_at: string | null;
203 mitigated_at: string | null;
204 resolved_at: string | null;
205 published_at: string | null;
206 commander: string | null;
207 communications: string | null;
208 created_by: string;
209 postmortem_published_at: string | null;
210 durations: IncidentDurations;
211 /** Follow-ups open and done. */
212 followups_open: number;
213 followups_done: number;
214 /** The last public update's time, for "updated 12 minutes ago". */
215 last_update_at: string | null;
216};
217
218export type AdminIncidentDetail = AdminIncident & {
219 /** Oldest first: public updates, notes and what changed. */
220 timeline: TimelineEntry[];
221 followups: FollowUp[];
222 postmortem: Postmortem | null;
223 /** What the postmortem editor starts from before anything is saved: the timeline filled in, follow-ups listed. */
224 postmortem_draft: PostmortemFields;
225 /** Its page on the status site. */
226 url: string;
Merge status detection: first-byte speed probe, deploy windows, 4 of 5 with a re-check, check history, reminders227 /**
228 * Every check of each of its parts around it, for the latency chart:
229 * from 30 minutes before it began to 30 minutes after it ended (or now),
230 * at most a day of it. Checks are kept for 7 days, so an older incident
231 * has none.
232 */
233 checks: CheckHistory[];
234};
235
236/** One check of one part, as the status worker keeps it (7 days). */
237export type CheckSample = {
238 component: string;
239 at: string;
240 /** How long it took; null when it took no time to speak of (a part with no check). */
241 ms: number | null;
242 outcome: "up" | "degraded" | "down";
243 /** The Cloudflare data centre the check ran from, from the answer's cf-ray; null when unknown. */
244 colo: string | null;
245 /** When the first try was slow and it was asked again at once: the first try's time. */
246 first_ms: number | null;
247};
248
249/** One part's checks over a span, and the time over which an answer counts as slow. */
250export type CheckHistory = {
251 key: string;
252 /** Slower than this counts as degraded; null when not known. */
253 slow_ms: number | null;
254 from: string;
255 to: string;
256 /** Oldest first. */
257 samples: CheckSample[];
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas258};
259
260export type AdminMaintenance = StatusMaintenance & { created_at: string; created_by: string };
261
262/** One line of the status worker's audit log: every staff change, with who made it. */
263export type StatusAuditEntry = { id: string; at: string; by: string; action: string; target: string; detail: string };
264
265export type ImpactInput = { key: string; impact: ComponentImpact };
266
267export type DeclareIncident = {
268 title: string;
269 severity: IncidentSeverity;
270 status?: IncidentStatus;
271 components: ImpactInput[];
272 /** The first public update. */
273 message: string;
274 /** When the impact began, if earlier than now. RFC 3339. */
275 started_at?: string | null;
276 commander?: string | null;
277 communications?: string | null;
278 /** Email subscribers about it. */
279 notify: boolean;
280 /** The staff member's email, kept with every line. */
281 by: string;
282};
283
284/** An update, a note, or a change, posted together. */
285export type IncidentChange = {
286 /** Shown on the status page (and emailed when `notify`), or a note only staff see. */
287 public: boolean;
288 message: string;
289 status?: IncidentStatus | null;
290 severity?: IncidentSeverity | null;
291 /** The parts' impact from now on; parts left out keep theirs. */
292 impacts?: ImpactInput[] | null;
293 notify?: boolean;
294 by: string;
295};
296
297export type RolesChange = { commander: string | null; communications: string | null; by: string };
298
299/** Putting a draft on the status page, with its first public update. */
300export type PublishIncident = { title?: string | null; message: string; notify: boolean; by: string };
301
302export type NewMaintenance = {
303 title: string;
304 message: string;
305 components: string[];
306 starts_at: string;
307 ends_at: string;
308 notify: boolean;
309 by: string;
310};
311
312export type MaintenanceChange = {
313 /** `update` posts a message; the others also move it along. */
314 action: "update" | "start" | "complete" | "cancel";
315 message: string;
316 notify: boolean;
317 by: string;
318};
319
320export type StatusBoard = {
321 /** Open and draft incidents, and those resolved in the last 180 days, newest first. */
322 incidents: AdminIncident[];
323 /** Upcoming, under way, and finished in the last 90 days. */
324 maintenance: AdminMaintenance[];
325 /** Confirmed email subscribers. */
326 subscribers: number;
327 /** Whether the status worker can send email (a sender and a token secret). */
328 email: boolean;
329};
330
331/**
332 * The status worker's `StatusAdmin` entrypoint. Only a service binding
333 * reaches it (sudo's `STATUS`); status.g1t.sh itself has no way to write.
334 * Every change is kept in its audit log with `by`.
335 */
336export interface StatusAdminApi {
337 /** The parts the page lists, for choosing which an incident affects. */
338 components(): Promise<{ key: string; name: string }[]>;
339 board(): Promise<StatusBoard>;
340 incident(id: string): Promise<AdminIncidentDetail | null>;
341 /** Open incidents, drafts included: the sidebar's count. */
342 openCount(): Promise<number>;
343 declare(input: DeclareIncident): Promise<Result<AdminIncident>>;
344 update(id: string, change: IncidentChange): Promise<Result<AdminIncident>>;
345 roles(id: string, change: RolesChange): Promise<Result<AdminIncident>>;
346 publish(id: string, input: PublishIncident): Promise<Result<AdminIncident>>;
347 dismiss(id: string, input: { reason: string; by: string }): Promise<Result<AdminIncident>>;
348 addFollowUp(id: string, input: { title: string; owner: string | null; by: string }): Promise<Result<FollowUp>>;
349 setFollowUp(id: string, followUp: string, input: { done: boolean; by: string }): Promise<Result<FollowUp>>;
350 savePostmortem(id: string, input: PostmortemFields & { by: string }): Promise<Result<Postmortem>>;
351 publishPostmortem(id: string, input: { publish: boolean; by: string }): Promise<Result<Postmortem>>;
352 scheduleMaintenance(input: NewMaintenance): Promise<Result<AdminMaintenance>>;
353 changeMaintenance(id: string, change: MaintenanceChange): Promise<Result<AdminMaintenance>>;
354 /** Newest first, 100 at a time, before `before`. */
355 audit(filter?: { before?: string | null }): Promise<StatusAuditEntry[]>;
356}
357
358// --- Words both sides use ------------------------------------------------------------
359
360/** What each severity means, for the team. Never shown on the status page. */
361export const INCIDENT_SEVERITIES: { value: IncidentSeverity; label: string; about: string; notify: boolean }[] = [
362 {
363 value: "sev1",
364 label: "SEV1",
365 about: "Critical: g1t is down or unusable for most people, or data is at risk. Everyone on it; a public update at least every 30 minutes.",
366 notify: true,
367 },
368 {
369 value: "sev2",
370 label: "SEV2",
371 about: "Major: a core part (sign-in, git, the API, agents) is broken or badly degraded for many people. A public update at least hourly.",
372 notify: true,
373 },
374 { value: "sev3", label: "SEV3", about: "Minor: one part is degraded or broken for some people, and there is a way around it.", notify: false },
375 { value: "sev4", label: "SEV4", about: "Low: little or no customer impact, such as a cosmetic fault or a risk caught before anyone noticed.", notify: false },
376];
377
378export const INCIDENT_STATUSES: { value: IncidentStatus; label: string; about: string }[] = [
379 { value: "investigating", label: "Investigating", about: "Something is wrong; the cause is not known yet." },
380 { value: "identified", label: "Identified", about: "The cause is known and a fix is under way." },
381 { value: "monitoring", label: "Monitoring", about: "A fix is out; watching that it holds." },
382 { value: "resolved", label: "Resolved", about: "Over. Closes the incident." },
383];
384
385export const COMPONENT_IMPACTS: { value: ComponentImpact; label: string }[] = [
386 { value: "operational", label: "Operational" },
387 { value: "degraded", label: "Degraded performance" },
388 { value: "partial_outage", label: "Partial outage" },
389 { value: "major_outage", label: "Major outage" },
390];

This file's history is long; its oldest lines are credited to the oldest commit read.