/** * Registry module-ownership administration (ce-1ch, SECURE_MODULE_PUBLISH.md D-C/D-E). * * The registry-server owns the allow/deny decision; these commands are the * first-class operator front door for the module-owner table: * celilo registry owner list — dump the whole table * celilo registry owner show — who owns one module name * celilo registry owner set — reassign (admin overwrite) * * Auth: the admin/bootstrap publish token is read from the local * publish_tokens list (resolveAdminToken) — these run on-mgr where that secret * lives, never resurfacing the raw admin value to a laptop. The registry URL * comes from --registry / CELILO_REGISTRY_URL / the built-in default. */ import { RegistryClient } from '../../registry/client'; import { getArg, getFlag, validateRequiredArgs } from '../parser'; import type { CommandResult } from '../types'; import { resolveAdminToken } from './registry-token'; type CommandError = Extract; function isError(v: unknown): v is CommandError { return typeof v === 'object' && v !== null && 'success' in v && v.success === false; } function clientFrom(flags: Record): RegistryClient { const registryUrl = getFlag(flags, 'registry', ''); return new RegistryClient(registryUrl || undefined); } function registryError(err: unknown): CommandError { return { success: false, error: `Registry request failed: ${err instanceof Error ? err.message : String(err)}`, }; } /** `celilo registry owner list` — dump the module-owner table. */ export async function handleRegistryOwnerList( flags: Record, ): Promise { const token = await resolveAdminToken(); if (isError(token)) return token; const client = clientFrom(flags); try { const owners = await client.listOwners(token); if (owners.length === 0) { return { success: true, message: 'No module ownerships recorded yet' }; } const nameWidth = Math.max(6, ...owners.map((o) => o.moduleName.length)); const subWidth = Math.max(5, ...owners.map((o) => o.ownerSub.length)); const header = `${'MODULE'.padEnd(nameWidth)} ${'OWNER'.padEnd(subWidth)} CLAIMED`; const rows = owners .slice() .sort((a, b) => a.moduleName.localeCompare(b.moduleName)) .map( (o) => `${o.moduleName.padEnd(nameWidth)} ${o.ownerSub.padEnd(subWidth)} ${o.claimedAt}`, ); return { success: true, message: [header, ...rows].join('\n') }; } catch (err) { return registryError(err); } } /** `celilo registry owner show ` — the owner of one module name. */ export async function handleRegistryOwnerShow( args: string[], flags: Record, ): Promise { const argErr = validateRequiredArgs(args, 1); if (argErr) { return { success: false, error: `${argErr}\n\nUsage: celilo registry owner show ` }; } const name = getArg(args, 0)?.trim(); if (!name) return { success: false, error: 'Module name is required' }; const token = await resolveAdminToken(); if (isError(token)) return token; const client = clientFrom(flags); try { const owner = await client.getOwner(name, token); if (!owner) { return { success: true, message: `${name} is unclaimed (first verified publisher owns it)` }; } return { success: true, message: `${name}\n owner: ${owner.ownerSub}\n claimed: ${owner.claimedAt}\n via: ${owner.sourceGroup}`, }; } catch (err) { return registryError(err); } } /** `celilo registry owner set ` — admin reassignment. */ export async function handleRegistryOwnerSet( args: string[], flags: Record, ): Promise { const argErr = validateRequiredArgs(args, 2); if (argErr) { return { success: false, error: `${argErr}\n\nUsage: celilo registry owner set `, }; } const name = getArg(args, 0)?.trim(); const ownerSub = getArg(args, 1)?.trim(); if (!name) return { success: false, error: 'Module name is required' }; if (!ownerSub) return { success: false, error: 'Owner subject is required' }; const token = await resolveAdminToken(); if (isError(token)) return token; const client = clientFrom(flags); try { const owner = await client.setOwner(name, ownerSub, token); return { success: true, message: `Reassigned ${owner.moduleName} → ${owner.ownerSub}` }; } catch (err) { return registryError(err); } }