Ctrl K
ComunidadGuías…

scripts/doctor.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env tsx
/**
 * `pnpm doctor` — diagnoses a Reccado checkout and tells you the exact command to
 * fix whatever is incomplete, instead of failing opaquely at `pnpm dev` or deploy.
 *
 * Default run is offline and deterministic (toolchain + local dev + config placeholders).
 * Pass `--cloud` to add remote checks (auth, D1 exists + id match, every declared Queue exists and
 * its DLQs are consumed, every sending domain publishes its lifecycle events to the events queue,
 * required secrets, the tg-hq forum topic registry agrees with D1's topic mappings, and — with `--url` — that the deployed login page and /api/health answer).
 * Exhaustive R2/queue/Email-Routing *binding* wiring lives in `pnpm verify:cf`.
 *
 * Usage:
 *   pnpm doctor                       # local + config checks for the default (production) config
 *   pnpm doctor --env dev             # inspect the env.dev block instead
 *   pnpm doctor --env dev --cloud --url https://…   # remote: D1, secrets, auth endpoints
 *   pnpm -s doctor:json               # machine-readable output (-s drops pnpm's banner; exit 1 on any fail)
 */
import { execFileSync } from "node:child_process";
import { existsSync, readdirSync, readFileSync } from "node:fs";
import { generatedConfigPathFor } from "./lib/built-config";
import {
	applyGeneratedOverlay,
	deployedVars,
	type WranglerConfig as DeployWranglerConfig,
	effectiveBlock,
	parseDomainList,
} from "./lib/deploy-config";
import { classifyDmarc, lookupTxt } from "./lib/dmarc";
import {
	type AccountQueue,
	crossCheckSendingDomains,
	describeFeedbackVerdict,
	EMAIL_SENDING_EVENT_TYPES,
	evaluateFeedbackSubscription,
	MIN_WRANGLER_FOR_EMAIL_SENDING,
	parseQueueListTable,
	parseSendingDomainsTable,
	parseSubscriptionListJson,
} from "./lib/event-subscriptions";
import {
	compareTopicRegistry,
	parseTopicRegistry,
	type TopicMapping,
} from "./lib/telegram-topic-registry";

type Status = "pass" | "warn" | "fail" | "info";
type Check = { id: string; status: Status; message: string; fix?: string };

const SYMBOL: Record<Status, string> = { pass: "✓", warn: "!", fail: "✗", info: "·" };
const PROD_D1_PLACEHOLDER = "<your-prod-d1-database-id>";
const DEV_D1_PLACEHOLDER = "00000000-0000-0000-0000-000000000000";
const EXAMPLE_FROM = "noreply@mail.example.com";

function parseArgs(argv: string[]): Record<string, string> {
	const args: Record<string, string> = {};
	for (let i = 0; i < argv.length; i += 1) {
		const arg = argv[i];
		if (!arg?.startsWith("--")) continue;
		const [rawKey, inlineValue] = arg.slice(2).split("=", 2);
		if (!rawKey) continue;
		if (inlineValue !== undefined) {
			args[rawKey] = inlineValue;
			continue;
		}
		const next = argv[i + 1];
		if (!next || next.startsWith("--")) {
			args[rawKey] = "true";
			continue;
		}
		args[rawKey] = next;
		i += 1;
	}
	return args;
}

function stripJsonc(input: string): string {
	return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
}

/** Compares dotted numeric versions. Returns -1/0/1 for a<b / a==b / a>b. */
function compareVersions(a: string, b: string): number {
	const pa = a.split(".").map((n) => Number.parseInt(n, 10) || 0);
	const pb = b.split(".").map((n) => Number.parseInt(n, 10) || 0);
	for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
		const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
		if (diff !== 0) return diff > 0 ? 1 : -1;
	}
	return 0;
}

function parseDotEnv(content: string): Map<string, string> {
	const map = new Map<string, string>();
	for (const line of content.split(/\r?\n/)) {
		const trimmed = line.trim();
		if (!trimmed || trimmed.startsWith("#")) continue;
		const sep = trimmed.indexOf("=");
		if (sep === -1) continue;
		map.set(trimmed.slice(0, sep).trim(), trimmed.slice(sep + 1).trim());
	}
	return map;
}

const checks: Check[] = [];
const add = (c: Check) => checks.push(c);

const args = parseArgs(process.argv.slice(2));
const targetEnv = args.env; // undefined => top-level (production) config
const asJson = args.json === "true";

// --- Toolchain ---------------------------------------------------------------

const pkg = JSON.parse(readFileSync("package.json", "utf8")) as {
	engines?: { node?: string };
	packageManager?: string;
};

const requiredNode = (pkg.engines?.node ?? "").replace(/[^\d.]/g, "");
const currentNode = process.versions.node;
if (requiredNode && compareVersions(currentNode, requiredNode) < 0) {
	add({
		id: "node.version",
		status: "fail",
		message: `Node ${currentNode} is below required >=${requiredNode}.`,
		fix: `Install Node >=${requiredNode} (see .node-version / your version manager).`,
	});
} else {
	add({
		id: "node.version",
		status: "pass",
		message: `Node ${currentNode} satisfies engines.node.`,
	});
}

const pinnedPnpm = pkg.packageManager?.startsWith("pnpm@")
	? pkg.packageManager.slice("pnpm@".length)
	: undefined;
try {
	const pnpmVersion = execFileSync("pnpm", ["--version"], { encoding: "utf8" }).trim();
	if (pinnedPnpm && pnpmVersion !== pinnedPnpm) {
		add({
			id: "pnpm.version",
			status: "warn",
			message: `pnpm ${pnpmVersion} differs from pinned pnpm@${pinnedPnpm}.`,
			fix: "Run `corepack enable` so the repo-pinned pnpm is used automatically.",
		});
	} else {
		add({
			id: "pnpm.version",
			status: "pass",
			message: `pnpm ${pnpmVersion} matches packageManager.`,
		});
	}
} catch {
	add({
		id: "pnpm.version",
		status: "fail",
		message: "pnpm is not runnable.",
		fix: "Enable it with `corepack enable` or install pnpm.",
	});
}

try {
	const wranglerVersion = execFileSync("pnpm", ["wrangler", "--version"], { encoding: "utf8" })
		.trim()
		.split(/\s+/)
		.find((t) => /^\d+\.\d+/.test(t));
	if (wranglerVersion && compareVersions(wranglerVersion, "4.0.0") >= 0) {
		add({ id: "wrangler.version", status: "pass", message: `Wrangler ${wranglerVersion} (4.x).` });
	} else {
		add({
			id: "wrangler.version",
			status: "warn",
			message: `Wrangler ${wranglerVersion ?? "unknown"} — this repo assumes 4.x.`,
			fix: "Update the `wrangler` devDependency to ^4.x.",
		});
	}
} catch {
	add({
		id: "wrangler.version",
		status: "fail",
		message: "Wrangler is not runnable.",
		fix: "Run `pnpm install` to install the pinned wrangler.",
	});
}

// --- Local dev ---------------------------------------------------------------

if (!existsSync(".dev.vars")) {
	add({
		id: "devvars.present",
		status: "info",
		message: ".dev.vars is absent — `pnpm dev` will generate a minimal one automatically.",
	});
} else {
	const devVars = parseDotEnv(readFileSync(".dev.vars", "utf8"));
	add({ id: "devvars.present", status: "pass", message: ".dev.vars exists." });

	const secret = devVars.get("BETTER_AUTH_SECRET")?.trim();
	if (!secret) {
		add({
			id: "devvars.auth-bypass",
			status: "pass",
			message: "BETTER_AUTH_SECRET is unset locally — local-dev bypass is active.",
		});
	} else if (secret.length < 32) {
		add({
			id: "devvars.auth-bypass",
			status: "fail",
			message:
				"BETTER_AUTH_SECRET is set but shorter than 32 characters — the issuer refuses to start.",
			fix: "Generate a real secret: openssl rand -base64 32",
		});
	} else {
		add({
			id: "devvars.auth-bypass",
			status: "pass",
			message: "BETTER_AUTH_SECRET is set — real Better Auth sessions locally.",
		});
	}
}

const migrationFiles = existsSync("migrations/d1")
	? readdirSync("migrations/d1").filter((f) => f.endsWith(".sql"))
	: [];
if (migrationFiles.length > 0) {
	add({
		id: "migrations.present",
		status: "pass",
		message: `${migrationFiles.length} D1 migration file(s) found.`,
	});
} else {
	add({
		id: "migrations.present",
		status: "fail",
		message: "No D1 migration files under migrations/d1.",
		fix: "Restore migrations/d1/*.sql (the schema source of truth).",
	});
}

// --- Config (wrangler.jsonc) -------------------------------------------------

type WranglerConfig = {
	name?: string;
	workers_dev?: boolean;
	routes?: Array<{ pattern: string; custom_domain?: boolean }>;
	vars?: { MAIL_FROM_ADDRESS?: string };
	queues?: {
		producers?: Array<{ binding: string; queue: string }>;
		consumers?: Array<{ queue: string; dead_letter_queue?: string }>;
	};
	d1_databases?: Array<{ binding: string; database_name?: string; database_id: string }>;
	env?: Record<string, WranglerConfig>;
};
const wrangler = JSON.parse(stripJsonc(readFileSync("wrangler.jsonc", "utf8"))) as WranglerConfig;
const block: WranglerConfig | undefined = targetEnv ? wrangler.env?.[targetEnv] : wrangler;
const envLabel = targetEnv ?? "production (top-level)";

if (!block) {
	add({
		id: "config.env",
		status: "fail",
		message: `No config block for env "${targetEnv}" in wrangler.jsonc.`,
		fix: "Pass a valid --env (e.g. dev) or omit it for the default config.",
	});
} else {
	const d1Id = block.d1_databases?.find((d) => d.binding === "INDEX_DB")?.database_id ?? "";
	const isPlaceholder = d1Id === PROD_D1_PLACEHOLDER || d1Id === DEV_D1_PLACEHOLDER || d1Id === "";
	if (!isPlaceholder) {
		add({
			id: "config.d1-id",
			status: "pass",
			message: `INDEX_DB database_id is set for ${envLabel}.`,
		});
	} else if (targetEnv) {
		add({
			id: "config.d1-id",
			status: "warn",
			message: `INDEX_DB database_id is a placeholder for ${envLabel} — fine locally, not for a remote deploy.`,
			fix: `pnpm setup:cloud --env ${targetEnv} --domain <d> --address inbox@<d> --apply (writes a gitignored generated config; don't edit wrangler.jsonc)`,
		});
	} else {
		add({
			id: "config.d1-id",
			status: "warn",
			message: `INDEX_DB database_id is a placeholder for ${envLabel} — local dev is fine, but a remote deploy will fail until it is set.`,
			fix: "pnpm setup:cloud --domain <d> --address inbox@<d> --apply (writes a gitignored generated config; don't edit wrangler.jsonc)",
		});
	}

	const from = block.vars?.MAIL_FROM_ADDRESS;
	if (from && from !== EXAMPLE_FROM) {
		add({
			id: "config.mail-from",
			status: "pass",
			message: `MAIL_FROM_ADDRESS set for ${envLabel}.`,
		});
	} else {
		add({
			id: "config.mail-from",
			status: "warn",
			message: `MAIL_FROM_ADDRESS is still the ${EXAMPLE_FROM} example for ${envLabel}.`,
			fix: "Set vars.MAIL_FROM_ADDRESS to a verified Email Sending address.",
		});
	}

	if (block.workers_dev === false) {
		add({
			id: "config.public-host",
			status: "pass",
			message: `${envLabel} disables workers.dev; use a custom domain for the deployed UI/API.`,
		});
	} else if (targetEnv === "dev") {
		add({
			id: "config.public-host",
			status: "info",
			message:
				"dev keeps workers.dev available for remote smoke tests; use a custom domain for the authenticated UI/API.",
			fix: `Attach a custom domain with pnpm setup:domain --env dev --hostname app.<your-domain>`,
		});
	} else {
		add({
			id: "config.public-host",
			status: "warn",
			message: `${envLabel} still exposes workers.dev.`,
			fix: `Set workers_dev=false and attach a custom domain with pnpm setup:domain${targetEnv ? ` --env ${targetEnv}` : ""} --hostname app.<your-domain>`,
		});
	}

	const customDomainRoutes =
		block.routes?.filter((route) => route.custom_domain).map((route) => route.pattern) ?? [];
	if (customDomainRoutes.length > 0) {
		add({
			id: "config.custom-domain",
			status: "pass",
			message: `Custom domain route configured: ${customDomainRoutes.join(", ")}.`,
		});
	} else {
		add({
			id: "config.custom-domain",
			status: targetEnv ? "info" : "warn",
			message: "No custom domain route is configured in tracked wrangler.jsonc.",
			fix: `Use pnpm setup:domain${targetEnv ? ` --env ${targetEnv}` : ""} --hostname app.<your-domain> (writes a gitignored generated config; doesn't edit wrangler.jsonc).`,
		});
	}
}

// --- Config that will actually deploy ------------------------------------------
//
// `pnpm run deploy[:dev]` ships wrangler.jsonc with the generated file's owned
// fields overlaid (scripts/lib/deploy-config.ts). Every check below that reasons
// about "the deployed config" reads it through the same function, so doctor and
// the deploy cannot disagree about what ships.

const generatedPath = generatedConfigPathFor(targetEnv);
let generatedConfig: DeployWranglerConfig | undefined;
try {
	generatedConfig = existsSync(generatedPath)
		? (JSON.parse(stripJsonc(readFileSync(generatedPath, "utf8"))) as DeployWranglerConfig)
		: undefined;
} catch {
	add({
		id: "config.deploy-overlay",
		status: "fail",
		message: `${generatedPath} exists but is not valid JSON — every deploy of ${envLabel} will refuse to run.`,
		fix: `Fix or delete ${generatedPath}, then re-run the setup script that wrote it.`,
	});
}
let deployVars: Record<string, unknown> = {};
if (block) {
	const trackedFull = wrangler as DeployWranglerConfig;
	try {
		deployVars = deployedVars(trackedFull, generatedConfig, targetEnv);
		if (generatedConfig) {
			const overlay = applyGeneratedOverlay({
				base: effectiveBlock(trackedFull, targetEnv),
				generated: effectiveBlock(generatedConfig, targetEnv),
				tracked: effectiveBlock(trackedFull, targetEnv),
			});
			// A generated var that overrides a DIFFERENT tracked value is the drift a
			// reader of wrangler.jsonc cannot see; one that only adds is the normal case.
			const conflicting = overlay.applied.filter(
				(change) => change.field.startsWith("vars.") && change.from !== undefined,
			);
			const ignoredNote =
				overlay.ignored.length > 0
					? ` Ignored there (wrangler.jsonc wins): ${overlay.ignored.join(", ")}.`
					: "";
			add(
				conflicting.length > 0
					? {
							id: "config.deploy-overlay",
							status: "warn",
							message: `${generatedPath} overrides wrangler.jsonc on deploy: ${conflicting.map((c) => `${c.field} ${JSON.stringify(c.from)} -> ${JSON.stringify(c.to)}`).join("; ")}.${ignoredNote}`,
							fix: "The generated value is what ships. Make the two agree (or delete the var from one file) so wrangler.jsonc does not mislead.",
						}
					: {
							id: "config.deploy-overlay",
							status: overlay.ignored.length > 0 ? "info" : "pass",
							message: `Deploys overlay ${generatedPath} (${overlay.applied.length} owned field(s) applied, none contradicting wrangler.jsonc).${ignoredNote}`,
						},
			);
		} else {
			add({
				id: "config.deploy-overlay",
				status: "info",
				message: `No ${generatedPath} — deploys ship wrangler.jsonc as built.`,
			});
		}
	} catch (error) {
		add({
			id: "config.deploy-overlay",
			status: "fail",
			message: `Could not compute the deployed config: ${error instanceof Error ? error.message : error}.`,
		});
	}
}

function addAll(items: Check[]): void {
	for (const c of items) add(c);
}

/** Confirms the target env's INDEX_DB database exists in the account and its id matches config. */
function checkD1Remote(): Check[] {
	const entry = block?.d1_databases?.find((d) => d.binding === "INDEX_DB");
	if (!entry?.database_name) return [];
	try {
		const list = JSON.parse(
			execFileSync("pnpm", ["wrangler", "d1", "list", "--json"], {
				encoding: "utf8",
				stdio: ["ignore", "pipe", "pipe"],
			}),
		) as Array<{ name: string; uuid?: string; database_id?: string }>;
		const found = list.find((d) => d.name === entry.database_name);
		if (!found) {
			return [
				{
					id: "cloud.d1",
					status: "fail",
					message: `D1 "${entry.database_name}" not found in this account.`,
					fix: `pnpm setup:cloud${targetEnv ? ` --env ${targetEnv}` : ""} --domain <d> --address inbox@<d> --apply`,
				},
			];
		}
		const realId = found.uuid ?? found.database_id;
		const isPlaceholder =
			entry.database_id === PROD_D1_PLACEHOLDER || entry.database_id === DEV_D1_PLACEHOLDER;
		if (!isPlaceholder && realId && realId !== entry.database_id) {
			return [
				{
					id: "cloud.d1",
					status: "warn",
					message: `D1 "${entry.database_name}" exists but its id (${realId}) differs from wrangler.jsonc (${entry.database_id}).`,
					fix: "Update the INDEX_DB database_id, or deploy with the generated wrangler config.",
				},
			];
		}
		return [
			{
				id: "cloud.d1",
				status: "pass",
				message: `D1 "${entry.database_name}" exists in the account.`,
			},
		];
	} catch {
		return [{ id: "cloud.d1", status: "warn", message: "Could not list D1 databases (auth?)." }];
	}
}

// `queues list` may or may not accept --json depending on the wrangler in play, so
// the first page decides which surface to read and the rest follow it: no repeated
// failures.
let queueListJson = true;

function runQueueList(page: number): string | null {
	const argv = ["wrangler", "queues", "list", "--page", String(page)];
	if (queueListJson) {
		try {
			return execFileSync("pnpm", [...argv, "--json"], {
				encoding: "utf8",
				stdio: ["ignore", "pipe", "pipe"],
			});
		} catch {
			queueListJson = false;
		}
	}
	try {
		return execFileSync("pnpm", argv, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
	} catch {
		return null;
	}
}

function parseQueueListJson(raw: string): AccountQueue[] | null {
	const start = raw.indexOf("[");
	if (start === -1) return null;
	try {
		const parsed = JSON.parse(raw.slice(start)) as unknown;
		if (!Array.isArray(parsed)) return null;
		return parsed.map((entry: Record<string, unknown>) => ({
			id: String(entry.queue_id ?? entry.id ?? ""),
			name: String(entry.queue_name ?? entry.name ?? ""),
			consumers: Array.isArray(entry.consumers)
				? entry.consumers.length
				: Number(entry.consumers_total_count ?? 0),
		}));
	} catch {
		return null;
	}
}

/**
 * Queue name → the account's row for it, or null if it can't be read. The id
 * matters as much as the consumer count: an event subscription's destination is
 * a queue id, so nothing can tell "subscribed to us" from "subscribed to the
 * other environment" without this mapping.
 */
function listAccountQueues(): Map<string, AccountQueue> | null {
	const byName = new Map<string, AccountQueue>();
	// Paginated: a queue that merely sits on page 2 would otherwise be reported as
	// a missing one, which is a deploy-breaking verdict.
	for (let page = 1; page <= 10; page += 1) {
		const raw = runQueueList(page);
		if (raw === null) return page === 1 ? null : byName;
		const rows = parseQueueListJson(raw) ?? parseQueueListTable(raw);
		if (rows.length === 0) break;
		for (const row of rows) {
			if (row.name) byName.set(row.name, row);
		}
	}
	return byName;
}

/**
 * Queues are the one binding wrangler never creates for you: deploy fails outright
 * on a queue the config names and the account lacks, and `pnpm doctor` existed
 * precisely so that failure arrives here instead. The declared set is the same
 * union setup:cloud provisions — producers, consumers and their dead-letter queues,
 * deduplicated — read the same way so the two cannot drift.
 *
 * A DLQ with no consumer is not a deploy failure but is worse in production: dead
 * messages sit there unread until Queues retention drops them (~4 days) and the
 * evidence of what broke is gone.
 */
function checkQueuesRemote(): Check[] {
	const dlqs = (block?.queues?.consumers ?? [])
		.map((consumer) => consumer.dead_letter_queue)
		.filter((name): name is string => Boolean(name));
	const declared = [
		...new Set([
			...(block?.queues?.producers ?? []).map((producer) => producer.queue),
			...(block?.queues?.consumers ?? []).map((consumer) => consumer.queue),
			...dlqs,
		]),
	].filter(Boolean);
	if (declared.length === 0) return [];

	const account = listAccountQueues();
	if (!account) {
		return [
			{
				id: "cloud.queues",
				status: "warn",
				message: "Could not list Queues (auth?).",
				fix: "Run `pnpm wrangler login`, then re-run with --cloud.",
			},
		];
	}

	const out: Check[] = [];
	const missing = declared.filter((name) => !account.has(name));
	if (missing.length > 0) {
		out.push({
			id: "cloud.queues",
			status: "fail",
			message: `Queue(s) declared for ${envLabel} but absent from this account: ${missing.join(", ")} — deploy will fail.`,
			fix: `pnpm setup:cloud${targetEnv ? ` --env ${targetEnv}` : ""} --domain <d> --address inbox@<d> --apply (or pnpm wrangler queues create ${missing[0]})`,
		});
	} else {
		out.push({
			id: "cloud.queues",
			status: "pass",
			message: `All ${declared.length} queue(s) declared for ${envLabel} exist in the account.`,
		});
	}

	// Only DLQs that exist can be judged on their consumers; the missing ones are
	// already a fail above, and saying "all DLQs have a consumer" about queues that
	// are not there would be the false reassurance this check exists to remove.
	const existingDlqs = dlqs.filter((name) => account.has(name));
	const orphanDlqs = existingDlqs.filter((name) => account.get(name)?.consumers === 0);
	if (orphanDlqs.length > 0) {
		out.push({
			id: "cloud.queues.dlq",
			status: "warn",
			message: `Dead-letter queue(s) with no consumer: ${orphanDlqs.join(", ")} — dead messages expire unread (~4 days) and leave no tombstone.`,
			fix: `Declare each DLQ under queues.consumers (the tombstone handler), then deploy — a consumer declared but never deployed looks exactly like this from the account side.`,
		});
	} else if (existingDlqs.length > 0) {
		out.push({
			id: "cloud.queues.dlq",
			status: "pass",
			message: `All ${existingDlqs.length} dead-letter queue(s) have a consumer.`,
		});
	}
	return out;
}

/**
 * Whether every sending domain Reccado owns can actually report what happened to
 * the mail it sends.
 *
 * Enabling Email Sending on a domain gives it the ability to send; only an event
 * subscription gives it the ability to *answer*. Nothing joined those two facts,
 * so the account ran for weeks with four enabled sending domains and one
 * subscription, and three features went on reading "no delivery event" as
 * evidence about the message rather than about the channel.
 *
 * Two live provider lists are crossed here, never a flag written at setup time:
 * a flag records what someone intended once, and the failure being caught is
 * precisely reality drifting from that intention.
 *
 * Existence alone is a false pass, so three things are checked: the subscription
 * exists, its event-type set covers all six, and its destination is this
 * environment's queue. A subscription with only `message.delivered` selected
 * satisfies a naive check while suppression stays dark.
 *
 * Scope is the sending domains sitting under a zone Reccado has registered in
 * D1. An account may host sending domains for other projects entirely, and
 * failing a Reccado diagnostic over someone else's subdomain would be noise that
 * teaches the operator to skip the whole check.
 */
function checkSendingFeedbackRemote(): Check[] {
	const queueName = block?.queues?.producers?.find(
		(producer) => producer.binding === "EMAIL_EVENTS_QUEUE",
	)?.queue;
	if (!queueName) return [];

	const account = listAccountQueues();
	const queueId = account?.get(queueName)?.id;
	if (!queueId) {
		// checkQueuesRemote already reports a missing or unreadable queue; repeating
		// it as a second failure would double-count one fault.
		return [
			{
				id: "cloud.sending-feedback",
				status: "warn",
				message: `Could not resolve the id of queue ${queueName}, so its event subscriptions cannot be checked.`,
				fix: "Run `pnpm wrangler login`, then re-run with --cloud.",
			},
		];
	}

	const ours = listRegisteredDomains();
	if (ours === null) {
		return [
			{
				id: "cloud.sending-feedback",
				status: "warn",
				message: "Could not read the registered domains from D1, so sending feedback is unchecked.",
				fix: `pnpm d1:migrate:${targetEnv ?? "prod"}, or check Cloudflare auth.`,
			},
		];
	}

	let sendingDomains: Array<{ zone: string; name: string; enabled: boolean }>;
	try {
		sendingDomains = parseSendingDomainsTable(
			execFileSync("pnpm", ["wrangler", "email", "sending", "list"], {
				encoding: "utf8",
				stdio: ["ignore", "pipe", "pipe"],
			}),
		);
	} catch {
		return [
			{
				id: "cloud.sending-feedback",
				status: "warn",
				message: "Could not list Email Sending domains (auth? open-beta command changed?).",
				fix: "pnpm wrangler email sending list",
			},
		];
	}

	const mine = sendingDomains.filter(
		(domain) => domain.enabled && ours.has(domain.zone.trim().toLowerCase()),
	);
	if (mine.length === 0) {
		return [
			{
				id: "cloud.sending-feedback",
				status: "info",
				message:
					ours.size === 0
						? "No domains are registered yet, so there is no sending feedback to check."
						: "No enabled Email Sending domain belongs to a registered domain yet.",
				fix: `pnpm setup:sending${targetEnv ? ` --env ${targetEnv}` : ""} --domain <your-domain> --apply`,
			},
		];
	}

	let subscriptions: ReturnType<typeof parseSubscriptionListJson>;
	try {
		subscriptions = parseSubscriptionListJson(
			execFileSync("pnpm", ["wrangler", "queues", "subscription", "list", queueName, "--json"], {
				encoding: "utf8",
				stdio: ["ignore", "pipe", "pipe"],
			}),
		);
	} catch {
		subscriptions = null;
	}
	if (!subscriptions) {
		return [
			{
				id: "cloud.sending-feedback",
				status: "warn",
				message: `Could not read the event subscriptions on ${queueName}.`,
				fix: `pnpm wrangler queues subscription list ${queueName} --json — needs wrangler >= ${MIN_WRANGLER_FOR_EMAIL_SENDING}.`,
			},
		];
	}

	const broken: string[] = [];
	for (const domain of mine) {
		const verdict = evaluateFeedbackSubscription({
			sendingDomain: domain.name,
			expectedQueueId: queueId,
			subscriptions,
		});
		if (verdict.state !== "live") {
			broken.push(describeFeedbackVerdict(domain.name, queueName, verdict));
		}
	}
	if (broken.length > 0) {
		return [
			{
				id: "cloud.sending-feedback",
				status: "fail",
				message: broken.join(" "),
				fix: `pnpm setup:sending${targetEnv ? ` --env ${targetEnv}` : ""} --domain <zone> --subdomain <label> --apply (or: pnpm wrangler queues subscription create ${queueName} --source email.sending --zone-id <zone-id> --domain <sending-domain> --events ${EMAIL_SENDING_EVENT_TYPES.join(",")})`,
			},
		];
	}
	return [
		{
			id: "cloud.sending-feedback",
			status: "pass",
			message: `All ${mine.length} sending domain(s) publish all ${EMAIL_SENDING_EVENT_TYPES.length} lifecycle events to ${queueName}.`,
		},
	];
}

/**
 * Whether MAIL_SENDING_DOMAINS — as the deploy will actually ship it, not as
 * wrangler.jsonc alone says — agrees with what Cloudflare Email Sending honours.
 *
 * Declared but not enabled is a FAIL: a mailbox on that domain sends as itself
 * and the provider refuses the From. Enabled and subscribed to our events queue
 * but not declared is only a WARN: the account may host other projects' sending
 * domains, and a mailbox there merely relays through MAIL_FROM_ADDRESS.
 */
/** Filled by checkSendingDomainsDeclared so the DMARC check covers them too. */
let subscribedButUndeclared: string[] = [];

function checkSendingDomainsDeclared(): Check[] {
	const declared = parseDomainList(deployVars.MAIL_SENDING_DOMAINS);
	const queueName = block?.queues?.producers?.find(
		(producer) => producer.binding === "EMAIL_EVENTS_QUEUE",
	)?.queue;
	const queueId = queueName ? listAccountQueues()?.get(queueName)?.id : undefined;
	let sendingDomains: ReturnType<typeof parseSendingDomainsTable>;
	let subscriptions: ReturnType<typeof parseSubscriptionListJson> = [];
	try {
		sendingDomains = parseSendingDomainsTable(
			execFileSync("pnpm", ["wrangler", "email", "sending", "list"], {
				encoding: "utf8",
				stdio: ["ignore", "pipe", "pipe"],
			}),
		);
		if (queueName) {
			subscriptions = parseSubscriptionListJson(
				execFileSync("pnpm", ["wrangler", "queues", "subscription", "list", queueName, "--json"], {
					encoding: "utf8",
					stdio: ["ignore", "pipe", "pipe"],
				}),
			);
		}
	} catch {
		return [
			{
				id: "cloud.sending-domains",
				status: "warn",
				message:
					"Could not list Email Sending domains or event subscriptions, so MAIL_SENDING_DOMAINS is unchecked.",
				fix: "pnpm wrangler email sending list",
			},
		];
	}
	const drift = crossCheckSendingDomains({
		declared,
		sendingDomains,
		subscriptions: subscriptions ?? [],
		queueId: queueId ?? "",
	});
	subscribedButUndeclared = queueId ? drift.undeclared : [];
	const out: Check[] = [];
	if (drift.unverified.length > 0) {
		out.push({
			id: "cloud.sending-domains",
			status: "fail",
			message: `MAIL_SENDING_DOMAINS (deployed) lists ${drift.unverified.join(", ")}, not enabled in Email Sending for this account — a mailbox there sends as itself and Cloudflare refuses the From.`,
			fix: `pnpm setup:sending${targetEnv ? ` --env ${targetEnv}` : ""} --domain <zone> --subdomain <label>|--apex --apply, or drop the entry from ${generatedConfig ? generatedPath : "wrangler.jsonc"} and redeploy.`,
		});
	} else {
		out.push({
			id: "cloud.sending-domains",
			status: "pass",
			message:
				declared.length > 0
					? `All ${declared.length} deployed MAIL_SENDING_DOMAINS entr${declared.length === 1 ? "y is" : "ies are"} enabled in Email Sending.`
					: "MAIL_SENDING_DOMAINS is empty in the deployed config; every mailbox relays through MAIL_FROM_ADDRESS.",
		});
	}
	if (!queueId) {
		out.push({
			id: "cloud.sending-domains",
			status: "warn",
			message: `Could not resolve the events queue${queueName ? ` ${queueName}` : ""}, so undeclared sending domains are unchecked.`,
		});
	} else if (drift.undeclared.length > 0) {
		out.push({
			id: "cloud.sending-domains",
			status: "warn",
			message: `Enabled in Email Sending and subscribed to ${queueName}, but not in the deployed MAIL_SENDING_DOMAINS: ${drift.undeclared.join(", ")} — a mailbox on one of them would send via MAIL_FROM_ADDRESS, not as itself. (Fine if they belong to another app or are only a fallback sender.)`,
			fix: `pnpm setup:sending${targetEnv ? ` --env ${targetEnv}` : ""} --domain <zone> --subdomain <label>|--apex --apply adds one to the generated config; then ${targetEnv === "dev" ? "pnpm run deploy:dev" : "pnpm run deploy"}.`,
		});
	}
	return out;
}

/**
 * What `_dmarc.<name>` publishes for each deployed MAIL_SENDING_DOMAINS entry,
 * plus every sending domain that feeds our events queue without being declared
 * — the names Email Sending was enabled on by hand, which is exactly where its
 * auto-created `p=reject` survives. Resolved over DNS-over-HTTPS, so it reports
 * what receivers see, not what the DNS API was last asked to write.
 */
async function checkDmarcRemote(): Promise<Check[]> {
	const names = [
		...new Set([...parseDomainList(deployVars.MAIL_SENDING_DOMAINS), ...subscribedButUndeclared]),
	];
	const out: Check[] = [];
	for (const domain of names) {
		const records = await lookupTxt(`_dmarc.${domain}`);
		if (records === null) {
			out.push({
				id: "cloud.dmarc",
				status: "warn",
				message: `_dmarc.${domain}: could not resolve over DNS-over-HTTPS.`,
				fix: `dig +short TXT _dmarc.${domain}`,
			});
			continue;
		}
		const verdict = classifyDmarc(domain, records);
		out.push({
			id: "cloud.dmarc",
			status: verdict.status,
			message: verdict.message,
			fix:
				verdict.status === "warn"
					? `pnpm setup:sending${targetEnv ? ` --env ${targetEnv}` : ""} --domain <zone> (--subdomain <label> | --apex) --dmarc-policy <none|quarantine|reject> --dmarc-rua dmarc@<zone> --apply`
					: undefined,
		});
	}
	return out;
}

/** The zones Reccado has registered, lowercased. Null when D1 can't be read. */
function listRegisteredDomains(): Set<string> | null {
	try {
		const raw = execFileSync(
			"pnpm",
			[
				"wrangler",
				"d1",
				"execute",
				"INDEX_DB",
				"--remote",
				"--json",
				"--command",
				"SELECT domain FROM domains WHERE status = 'active'",
				...(targetEnv ? ["--env", targetEnv] : []),
			],
			{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
		);
		const parsed = JSON.parse(raw.slice(raw.indexOf("["))) as Array<{
			results: Array<{ domain: string }>;
		}>;
		return new Set((parsed[0]?.results ?? []).map((row) => row.domain.trim().toLowerCase()));
	} catch {
		return null;
	}
}

/** Mirrors src/telegram/registration.ts: two minutes of clock-skew tolerance. */
const TELEGRAM_CLOCK_SKEW_MS = 120_000;

/**
 * Reads the four facts that decide whether the Telegram bridge actually delivers:
 * the chat it adopted, the origin it learned to register its webhook on, the
 * registration it performed, and what Telegram last observed about it. All live
 * in runtime_config because the worker observes them -- so a bridge that is
 * configured but mute has somewhere to say so, which is exactly what it lacked
 * when it sat dead in production for days.
 *
 * The last pair catches the mute case the first pair cannot see: a webhook on the
 * right URL registered with a secret Telegram's updates no longer match, which
 * answers 401 on every delivery while every other signal reads healthy.
 */
function checkTelegramBridge(): Check {
	let rows: Array<{ key: string; value: string }>;
	try {
		const raw = execFileSync(
			"pnpm",
			[
				"wrangler",
				"d1",
				"execute",
				"INDEX_DB",
				"--remote",
				"--json",
				"--command",
				"SELECT key, value FROM runtime_config WHERE key IN ('telegram.chat_id', 'deployment.origin', 'telegram.webhook_registration', 'telegram.webhook_observation')",
				...(targetEnv ? ["--env", targetEnv] : []),
			],
			{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
		);
		const parsed = JSON.parse(raw.slice(raw.indexOf("["))) as Array<{
			results: Array<{ key: string; value: string }>;
		}>;
		rows = parsed[0]?.results ?? [];
	} catch {
		return {
			id: "cloud.telegram",
			status: "warn",
			message: "TELEGRAM_BOT_TOKEN is set but runtime_config could not be read.",
			fix: `pnpm d1:migrate:${targetEnv ?? "prod"} — the runtime_config table may be missing.`,
		};
	}
	const byKey = new Map(rows.map((row) => [row.key, row.value]));

	// The live pairing code, if the bridge is still waiting for its first operator.
	// `strftime` and not `datetime('now')`: expires_at is written as toISOString(), and
	// the two formats differ at character 11 (T vs space) where the space sorts lower —
	// with datetime() every code would look unexpired forever.
	let pairingCode: string | null = null;
	let operatorCount = 0;
	try {
		const rawOwners = execFileSync(
			"pnpm",
			[
				"wrangler",
				"d1",
				"execute",
				"INDEX_DB",
				"--remote",
				"--json",
				"--command",
				"SELECT (SELECT COUNT(*) FROM owner_identities WHERE kind = 'telegram') AS operators, (SELECT code FROM owner_pairing_codes WHERE consumed_at IS NULL AND expires_at > strftime('%Y-%m-%dT%H:%M:%fZ','now') ORDER BY created_at DESC LIMIT 1) AS code",
				...(targetEnv ? ["--env", targetEnv] : []),
			],
			{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
		);
		const parsedOwners = JSON.parse(rawOwners.slice(rawOwners.indexOf("["))) as Array<{
			results: Array<{ operators: number; code: string | null }>;
		}>;
		const row = parsedOwners[0]?.results?.[0];
		operatorCount = row?.operators ?? 0;
		pairingCode = row?.code ?? null;
	} catch {
		// Registry unreadable (migration 0012 not applied yet); the checks below still
		// report what they can rather than failing the whole doctor run.
	}

	const missing: string[] = [];
	if (!byKey.get("deployment.origin")) {
		missing.push("no deployment origin recorded (load the authenticated UI once)");
	}
	if (operatorCount === 0) {
		missing.push(
			pairingCode
				? `no operator linked — send /start ${pairingCode} to the bot`
				: "no operator linked (the hourly cron mints a pairing code; re-run once it has)",
		);
	} else if (!byKey.get("telegram.chat_id")) {
		missing.push("no chat adopted (send /start to the bot from a linked account)");
	}
	if (missing.length > 0) {
		return {
			id: "cloud.telegram",
			status: "warn",
			message: `Telegram bridge is on but not delivering: ${missing.join("; ")}.`,
			fix: "Both are one-time actions; the hourly cron registers the webhook afterwards.",
		};
	}
	const registration = parseJsonValue(byKey.get("telegram.webhook_registration"));
	const observation = parseJsonValue(byKey.get("telegram.webhook_observation"));
	const registeredAt = Date.parse(String(registration?.registeredAt ?? ""));
	const lastErrorAt = Date.parse(String(observation?.lastErrorAt ?? ""));
	// Only the dates are compared; the message is quoted, never parsed -- Telegram
	// owns that wording and may reword it whenever it likes.
	if (
		Number.isFinite(registeredAt) &&
		Number.isFinite(lastErrorAt) &&
		lastErrorAt > registeredAt + TELEGRAM_CLOCK_SKEW_MS
	) {
		return {
			id: "cloud.telegram",
			status: "warn",
			message: `Telegram is failing to deliver webhook updates since ${observation?.lastErrorAt}: ${observation?.lastErrorMessage ?? "no message reported"} (${observation?.pendingUpdateCount ?? 0} update(s) pending).`,
			fix: "The hourly cron re-registers the webhook; if the error persists, check the Telegram webhook URL and secret.",
		};
	}
	return {
		id: "cloud.telegram",
		status: "pass",
		message: `Telegram bridge delivers to chat ${byKey.get("telegram.chat_id")} via ${byKey.get("deployment.origin")}.`,
	};
}

const TG_HQ_ITEM = "op://Personal/Telegram - Santi HQ bot";

/**
 * One field of the tg-hq 1Password item. Env first (TG_HQ_TOPICS, TG_HQ_CHAT_ID),
 * as tg-hq itself reads it, for machines without `op`. Only the non-secret
 * fields are ever read here; the bot token (`credential`) never is.
 */
function readTgHqField(field: "topics" | "chat_id"): { value: string } | { skip: string } {
	const fromEnv = process.env[field === "topics" ? "TG_HQ_TOPICS" : "TG_HQ_CHAT_ID"];
	if (fromEnv?.trim()) return { value: fromEnv.trim() };
	try {
		const value = execFileSync("op", ["read", `${TG_HQ_ITEM}/${field}`], {
			encoding: "utf8",
			stdio: ["ignore", "pipe", "pipe"],
			timeout: 30_000,
		}).trim();
		return { value };
	} catch (error) {
		const code = (error as NodeJS.ErrnoException).code;
		if (code === "ENOENT") return { skip: "the 1Password CLI (`op`) is not installed" };
		if (code === "ETIMEDOUT") return { skip: "`op` did not answer in 30 s (1Password locked?)" };
		return {
			skip: `could not read "${field}" from the tg-hq 1Password item (missing, or op signed out)`,
		};
	}
}

/**
 * `cloud.telegram-topics`: the tg-hq registry of the owner's HQ forum against
 * D1 `telegram_topics` for the bound chat. Read-only on both sides, and it never
 * calls Telegram — the Bot API's only topic probe is `editForumTopic`, which
 * renames the topic it touches.
 */
function checkTelegramTopicsRemote(): Check[] {
	const id = "cloud.telegram-topics";
	const topicsField = readTgHqField("topics");
	if ("skip" in topicsField) {
		return [{ id, status: "info", message: `Skipped: ${topicsField.skip}.` }];
	}
	const chatField = readTgHqField("chat_id");
	if ("skip" in chatField || !chatField.value) {
		return [
			{
				id,
				status: "info",
				message: `Skipped: ${"skip" in chatField ? chatField.skip : "the tg-hq item has no chat_id"}.`,
			},
		];
	}
	const registry = parseTopicRegistry(topicsField.value);
	if (!registry.ok) {
		return [
			{
				id,
				status: "warn",
				message: `tg-hq registry is unreadable: ${registry.error}.`,
				fix: "Run `tg-hq topics` to see what tg-hq itself makes of it.",
			},
		];
	}
	let bound: string | null;
	let mappings: TopicMapping[];
	try {
		const raw = execFileSync(
			"pnpm",
			[
				"wrangler",
				"d1",
				"execute",
				"INDEX_DB",
				"--remote",
				"--json",
				"--command",
				"SELECT value FROM runtime_config WHERE key = 'telegram.chat_id'; SELECT t.chat_id, t.mailbox_id, t.topic_id, m.primary_address FROM telegram_topics t LEFT JOIN mailboxes m ON m.mailbox_id = t.mailbox_id",
				...(targetEnv ? ["--env", targetEnv] : []),
			],
			{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
		);
		const parsed = JSON.parse(raw.slice(raw.indexOf("["))) as [
			{ results: Array<{ value: string }> },
			{
				results: Array<{
					chat_id: string | number;
					mailbox_id: string;
					topic_id: number;
					primary_address: string | null;
				}>;
			},
		];
		bound = parsed[0]?.results?.[0]?.value ?? null;
		mappings = (parsed[1]?.results ?? []).map((row) => ({
			chatId: String(row.chat_id),
			mailboxId: row.mailbox_id,
			address: row.primary_address,
			topicId: Number(row.topic_id),
		}));
	} catch {
		return [
			{
				id,
				status: "warn",
				message: "Could not read runtime_config / telegram_topics from D1.",
				fix: `pnpm d1:migrate:${targetEnv ?? "prod"} — the telegram_topics table may be missing.`,
			},
		];
	}
	return compareTopicRegistry({
		boundChatId: bound,
		registryChatId: chatField.value,
		registry: registry.topics,
		mappings,
	}).map((finding) => ({ id, ...finding }));
}

/** Runtime-config JSON values, read leniently: a malformed row is not a diagnosis. */
function parseJsonValue(raw: string | undefined): Record<string, unknown> | null {
	if (!raw) return null;
	try {
		const parsed = JSON.parse(raw) as unknown;
		return typeof parsed === "object" && parsed !== null
			? (parsed as Record<string, unknown>)
			: null;
	} catch {
		return null;
	}
}

/** Confirms the Worker's remote auth secret. */
function checkSecretsRemote(): Check[] {
	let names: Set<string>;
	try {
		const secrets = JSON.parse(
			execFileSync(
				"pnpm",
				[
					"wrangler",
					"secret",
					"list",
					"--format",
					"json",
					...(targetEnv ? ["--env", targetEnv] : []),
				],
				{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] },
			),
		) as Array<{ name: string }>;
		names = new Set(secrets.map((s) => s.name));
	} catch {
		return [
			{
				id: "cloud.secrets",
				status: "warn",
				message: `Could not list secrets for ${targetEnv ? `env "${targetEnv}"` : "the default Worker"} (deployed?).`,
			},
		];
	}
	const out: Check[] = [];
	// The bridge is optional, so its absence is a pass, not a warning. What used to
	// be invisible -- a bot token set but nothing ever delivered -- is what this
	// reports, by reading the state the worker records about itself.
	if (names.has("TELEGRAM_BOT_TOKEN")) {
		out.push(checkTelegramBridge());
	}
	const missingAuth = ["BETTER_AUTH_SECRET"].filter((n) => !names.has(n));
	out.push(
		missingAuth.length === 0
			? { id: "cloud.secret.auth", status: "pass", message: "Better Auth secret is set." }
			: {
					id: "cloud.secret.auth",
					status: "warn",
					message: `Auth secret(s) missing: ${missingAuth.join(", ")} — the web login cannot issue sessions without it.`,
					fix: "openssl rand -base64 32 && pnpm wrangler secret put BETTER_AUTH_SECRET",
				},
	);
	return out;
}

/**
 * The web perimeter is now in the worker, so "is the perimeter actually up" is
 * two plain HTTP probes instead of an edge-redirect shape: the login page must
 * be reachable, and /api/health — deliberately unauthenticated — must answer.
 * A 404 on /login or a dead /api/health means the deploy or the route is wrong
 * before any question of sessions even arises.
 */
async function checkAuthEndpoints(rawUrl: string): Promise<Check[]> {
	const healthUrl = new URL("/api/health", rawUrl).toString();
	const loginUrl = new URL("/login", rawUrl).toString();
	if (healthUrl.includes(".workers.dev/")) {
		return [
			{
				id: "cloud.auth",
				status: "warn",
				message: `Auth endpoint check skipped for ${healthUrl}: Reccado's supported public path is a custom domain, not workers.dev.`,
				fix: `Attach a custom domain first with pnpm setup:domain${targetEnv ? ` --env ${targetEnv}` : ""} --hostname app.<your-domain>`,
			},
		];
	}
	const controller = new AbortController();
	const timeout = setTimeout(() => controller.abort(), 8000);
	try {
		const [health, login] = await Promise.all([
			fetch(healthUrl, { redirect: "manual", signal: controller.signal }),
			fetch(loginUrl, { redirect: "follow", signal: controller.signal }),
		]);
		const out: Check[] = [];
		if (health.status === 200) {
			out.push({
				id: "cloud.auth.health",
				status: "pass",
				message: `Unauthenticated ${healthUrl} answers (health is deliberately public).`,
			});
		} else {
			out.push({
				id: "cloud.auth.health",
				status: "fail",
				message: `Unauthenticated ${healthUrl} returned ${health.status} (expected 200).`,
				fix: "Check that the Worker is deployed and the /api/health route is reachable.",
			});
		}
		if (login.ok) {
			out.push({
				id: "cloud.auth.login",
				status: "pass",
				message: `The login page ${loginUrl} is reachable.`,
			});
		} else {
			out.push({
				id: "cloud.auth.login",
				status: "fail",
				message: `The login page ${loginUrl} returned ${login.status}.`,
				fix: "Check that the deployed build includes the /login route (pnpm run build, then redeploy).",
			});
		}
		return out;
	} catch {
		return [
			{
				id: "cloud.auth",
				status: "warn",
				message: `Could not reach ${healthUrl} or ${loginUrl} to check the auth endpoints.`,
				fix: "Check the URL and that the Worker is deployed.",
			},
		];
	} finally {
		clearTimeout(timeout);
	}
}

// --- Cloud (opt-in) ----------------------------------------------------------
//
// Runs here, below every declaration it touches, and not up with the local checks
// where it used to sit: `runQueueList` reads a module-level `let`, and calling it
// from above that declaration threw a TDZ ReferenceError that killed the entire
// process — so `--cloud` diagnosed nothing at all, including the checks that were
// passing before it. A diagnostic that crashes is worse than one that fails.

if (args.cloud === "true") {
	try {
		const who = execFileSync("pnpm", ["wrangler", "whoami"], { encoding: "utf8" });
		const email = who.match(/[\w.+-]+@[\w.-]+/)?.[0];
		add({
			id: "cloud.auth",
			status: "pass",
			message: `Authenticated with Cloudflare${email ? ` as ${email}` : ""}.`,
		});
	} catch {
		add({
			id: "cloud.auth",
			status: "warn",
			message: "Not authenticated with Cloudflare (cloud checks skipped).",
			fix: "Run `pnpm wrangler login`, then re-run with --cloud.",
		});
	}
	addAll(checkD1Remote());
	addAll(checkQueuesRemote());
	addAll(checkSendingFeedbackRemote());
	addAll(checkSendingDomainsDeclared());
	addAll(await checkDmarcRemote());
	addAll(checkSecretsRemote());
	addAll(checkTelegramTopicsRemote());
	if (args.url) {
		addAll(await checkAuthEndpoints(args.url));
	} else {
		add({
			id: "cloud.auth",
			status: "info",
			message:
				"Pass --url <deployed-url> to check that the login page and /api/health answer on the deployment.",
		});
	}
	add({
		id: "cloud.bindings",
		status: "info",
		message:
			"Exhaustive binding verification (R2/queues/Email Routing wiring) lives in `pnpm verify:cf`.",
	});
}

// --- Report ------------------------------------------------------------------

const counts = { pass: 0, warn: 0, fail: 0, info: 0 } as Record<Status, number>;
for (const c of checks) counts[c.status] += 1;

if (asJson) {
	console.log(JSON.stringify({ env: envLabel, counts, checks }, null, 2));
} else {
	console.log(`\nReccado doctor — config: ${envLabel}\n`);
	for (const c of checks) {
		console.log(`  ${SYMBOL[c.status]} ${c.id.padEnd(24)} ${c.message}`);
		if (c.fix && (c.status === "warn" || c.status === "fail")) {
			console.log(`      → ${c.fix}`);
		}
	}
	console.log(
		`\n  ${counts.pass} pass · ${counts.warn} warn · ${counts.fail} fail · ${counts.info} info\n`,
	);
}

process.exit(counts.fail > 0 ? 1 : 0);