/** * `celilo dns` — operator views over celilo's DNS bookkeeping. * * `registrations` lists the dns_registrations ledger: every (provider, * fqdn) the framework has registered via dns_registrar.registerHost, * with every module that depends on it and when it was last re-asserted * by the provider's refresh_registrations hook. Read-only; names only * (module ids), never UUIDs. See * designs/DISPATCHER_DAEMON_AND_TIMER_EVENTS.md (B2b). */ import { getDb } from '../../db/client'; import { listDnsRegistrations } from '../../services/dns-registrations'; import type { CommandResult } from '../types'; function formatAge(from: Date | null, now: Date): string { if (!from) return 'never'; const mins = Math.floor((now.getTime() - from.getTime()) / 60_000); if (mins < 1) return 'just now'; if (mins < 60) return `${mins}m ago`; const hours = Math.floor(mins / 60); if (hours < 48) return `${hours}h ago`; return `${Math.floor(hours / 24)}d ago`; } export async function handleDnsRegistrations( _args: string[], flags: Record, ): Promise { const db = getDb(); const provider = typeof flags.provider === 'string' ? flags.provider : undefined; const rows = listDnsRegistrations(db, { providerModuleId: provider }); if (rows.length === 0) { const scope = provider ? ` for provider '${provider}'` : ''; return { success: true, message: `No DNS registrations recorded${scope}\n\nRegistrations are recorded when a module registers a hostname via the dns_registrar capability (e.g. during deploy).`, }; } const now = new Date(); // No address column: the ledger stores none. What a name actually resolves // to publicly is what `celilo system audit`'s public_dns check reports, from // off-fleet — reading it back out of celilo's own table is what made a // nine-day outage invisible (design.md D1). const header = ['FQDN', 'KIND', 'PROVIDER', 'CONSUMERS', 'REGISTERED', 'REFRESHED']; const table = rows.map((r) => [ r.fqdn, r.companion ? 'companion' : 'declared', r.providerModuleId, r.consumerModuleIds.join(', ') || '—', formatAge(r.registeredAt, now), formatAge(r.refreshedAt, now), ]); const widths = header.map((h, i) => Math.max(h.length, ...table.map((row) => row[i].length))); const render = (row: string[]) => row.map((cell, i) => cell.padEnd(widths[i])).join(' '); const lines = [render(header), ...table.map(render)]; return { success: true, message: lines.join('\n'), data: rows }; }