1
19
20export interface CheckResult {
21 label: string;
22 status: "PASS" | "FAIL" | "SKIP";
23 detail: string;
24 ageMs: number | null;
25}
26
27
28export function ageMsSince(iso: string, now: Date): number {
29 return now.getTime() - new Date(iso).getTime();
30}
31
32
33export function fmtAge(ms: number): string {
34 const min = Math.round(ms / 60000);
35 if (min < 60) return `${min}m`;
36 const h = ms / 3600000;
37 return `${h.toFixed(1)}h`;
38}
39
40
45export function isAuthError(msg: string): boolean {
46 return /HTTP 403|SignatureDoesNotMatch|AccessDenied|InvalidAccessKey|InvalidClientTokenId|Credentials|unauthorized/i.test(
47 msg,
48 );
49}
50
51
58export function recordUnavailable(
59 results: CheckResult[],
60 label: string,
61 err: unknown,
62 limit: string,
63 strict: boolean,
64): void {
65 const msg = err instanceof Error ? err.message : String(err);
66 if (isAuthError(msg)) {
67 results.push({ label, status: "FAIL", ageMs: null, detail: `AUTH ERROR: ${msg} (limit ${limit})` });
68 return;
69 }
70 if (strict) {
71 results.push({ label, status: "FAIL", ageMs: null, detail: `UNAVAILABLE (strict): ${msg} (limit ${limit})` });
72 return;
73 }
74 results.push({
75 label,
76 status: "SKIP",
77 ageMs: null,
78 detail: `UNAVAILABLE — report-only skip (dev, NOT a pass): ${msg} (limit ${limit})`,
79 });
80}
81
82export interface Verdict {
83 exitCode: number;
84 message: string;
85 failures: CheckResult[];
86 skips: CheckResult[];
87}
88
89
96export function verdictFor(results: CheckResult[]): Verdict {
97 const failures = results.filter((r) => r.status === "FAIL");
98 const skips = results.filter((r) => r.status === "SKIP");
99 if (failures.length > 0) {
100 return {
101 exitCode: 1,
102 message: `FAIL — ${failures.length} check(s) not healthy`,
103 failures,
104 skips,
105 };
106 }
107 if (skips.length > 0) {
108 return {
109 exitCode: 0,
110 message: `OK with ${skips.length} report-only skip(s) (unreachable — dev mode). No failures.`,
111 failures,
112 skips,
113 };
114 }
115 return {
116 exitCode: 0,
117 message: `OK — all ${results.length} checks healthy`,
118 failures,
119 skips,
120 };
121}
122