import "server-only"; import { satisfiesCapability } from "../capability-context"; import { fail, type HousekeepingCapabilityContext, type HousekeepingErrorCode, type HousekeepingInboxSource, type HousekeepingWorkItem, ok, } from "../contracts"; import { createCorrelationId } from "../correlation"; import { isSafeHousekeepingHref } from "../housekeeping-href"; import { type HousekeepingProvider, runProvider, } from "../providers/run-provider"; import { createHousekeepingRegistry, type HousekeepingRegistry, } from "../registry"; export const INBOX_POLICY = Object.freeze({ timeoutMs: 2_000, combinedLimit: 200, dedupe: "sourceId:itemId", } as const); export interface HousekeepingInboxResponse { readonly items: readonly HousekeepingWorkItem[]; readonly errors: readonly { readonly sourceId: string; readonly code: HousekeepingErrorCode; }[]; readonly correlationId: string; } export interface HousekeepingInboxServiceDependencies { readonly registry: HousekeepingRegistry; readonly createCorrelationId?: () => string; readonly timeoutMs?: number; } const priorityRank = { critical: 0, high: 1, normal: 2, low: 3, } as const; const severities = new Set(["info", "warning", "critical"]); const priorities = new Set(Object.keys(priorityRank)); const freshnessValues = new Set(["fresh", "stale"]); function hasUsableCapability( value: unknown, ): value is HousekeepingWorkItem["capability"] { if (value === null || typeof value !== "object") return false; const capability = value as { mode?: unknown; slugs?: unknown }; return ( (capability.mode === "any" || capability.mode === "all") && Array.isArray(capability.slugs) && capability.slugs.length > 0 && capability.slugs.every((slug) => typeof slug === "string") ); } function isValidWorkItem( item: HousekeepingWorkItem, source: HousekeepingInboxSource, context: HousekeepingCapabilityContext, ): boolean { return ( item !== null && typeof item === "object" && item.sourceId === source.id && item.domain === source.owner && typeof item.itemId === "string" && item.itemId.trim().length > 0 && typeof item.deduplicationKey === "string" && item.deduplicationKey.trim().length > 0 && severities.has(item.severity) && priorities.has(item.priority) && Number.isFinite(item.ageMs) && item.ageMs >= 0 && typeof item.state === "string" && item.state.trim().length > 0 && typeof item.occurredAt === "string" && Number.isFinite(Date.parse(item.occurredAt)) && typeof item.titleKey === "string" && item.titleKey.trim().length > 0 && isSafeHousekeepingHref(item.href) && freshnessValues.has(item.freshness) && Array.isArray(item.actions) && hasUsableCapability(item.capability) && satisfiesCapability(context, item.capability) ); } function isPermittedAction( action: HousekeepingWorkItem["actions"][number], context: HousekeepingCapabilityContext, ): boolean { return ( action !== null && typeof action === "object" && typeof action.id === "string" && action.id.trim().length > 0 && typeof action.labelKey === "string" && action.labelKey.trim().length > 0 && typeof action.commandId === "string" && action.commandId.trim().length > 0 && hasUsableCapability(action.capability) && satisfiesCapability(context, action.capability) ); } function sourceProvider( source: HousekeepingInboxSource, ): HousekeepingProvider { return { id: source.id, capability: source.capability, async run(context, signal) { const result = await source.getItems(context, signal); if (!result.ok) return result; if (result.data.availability !== "available") { return fail( "DEPENDENCY_UNAVAILABLE", "errors.housekeeping.dependencyUnavailable", result.correlationId, ); } return ok(result.data.items, result.correlationId); }, }; } export function createHousekeepingInboxService({ registry, createCorrelationId: correlationIdFactory = createCorrelationId, timeoutMs = INBOX_POLICY.timeoutMs, }: HousekeepingInboxServiceDependencies) { return async function loadInbox( context: HousekeepingCapabilityContext, ): Promise { const sources = registry.domains.flatMap((domain) => { if (!satisfiesCapability(context, domain.capability)) return []; return domain.inboxSources.filter((source) => satisfiesCapability(context, source.capability), ); }); const outcomes = await Promise.all( sources.map((source) => runProvider(sourceProvider(source), context, timeoutMs), ), ); const sourceById = new Map(sources.map((source) => [source.id, source])); const seen = new Set(); const items: HousekeepingWorkItem[] = []; for (const outcome of outcomes) { const source = sourceById.get(outcome.providerId); if (!source) continue; for (const item of outcome.items) { if (!isValidWorkItem(item, source, context)) continue; const key = JSON.stringify([item.sourceId, item.itemId]); if (seen.has(key)) continue; seen.add(key); items.push({ ...item, actions: item.actions.filter((action) => isPermittedAction(action, context), ), }); } } items.sort((left, right) => { const priority = priorityRank[left.priority] - priorityRank[right.priority]; if (priority !== 0) return priority; return right.ageMs - left.ageMs; }); return { items: items.slice(0, INBOX_POLICY.combinedLimit), errors: outcomes.flatMap((outcome) => outcome.error ? [{ sourceId: outcome.providerId, code: outcome.error }] : [], ), correlationId: correlationIdFactory(), }; }; } export async function loadHousekeepingInbox( context: HousekeepingCapabilityContext, ): Promise { const { HOUSEKEEPING_MANIFESTS } = await import("../../manifests"); const service = createHousekeepingInboxService({ registry: createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS), }); return service(context); }