import "server-only"; import type { AuditEntry } from "@/lib/services/audit"; import { satisfiesCapability } from "../capability-context"; import { type CanonicalHousekeepingHref, fail, type HousekeepingCapabilityContext, type HousekeepingDomainManifest, type HousekeepingResult, ok, } from "../contracts"; import { createCorrelationId } from "../correlation"; import type { HousekeepingRegistry } from "../registry"; export const RECENT_WORK_POLICY = Object.freeze({ limit: 12, scanLimit: 200, dedupe: "routeId", } as const); export interface HousekeepingRecentItem { readonly routeId: string; readonly canonicalHref: CanonicalHousekeepingHref; readonly labelKey: string; readonly occurredAt: string; readonly source: "route-visit" | "audit"; } export interface HousekeepingRecentAuditRow { readonly action: string; readonly target: string; readonly domain: string | null; readonly outcome: string | null; readonly createdAt: string; } interface RegisteredRoute { readonly domain: HousekeepingDomainManifest; readonly route: HousekeepingDomainManifest["routes"][number]; } export interface HousekeepingRecentWorkDependencies { readonly registry: HousekeepingRegistry; readonly loadRows: ( userId: number, limit: number, ) => Promise; readonly writeAudit: (entry: AuditEntry) => Promise; readonly createCorrelationId?: () => string; } const completedOutcomes = new Set(["success", "partial"]); const genericActionTokens = new Set([ "add", "apply", "change", "create", "delete", "execute", "remove", "runtime", "save", "set", "toggle", "update", ]); function registeredRoutes( registry: HousekeepingRegistry, ): readonly RegisteredRoute[] { return registry.domains.flatMap((domain) => domain.routes.map((route) => ({ domain, route })), ); } function normalizedTokens(value: string): readonly string[] { return value .toLocaleLowerCase() .split(/[^a-z0-9]+/) .filter(Boolean) .map((token) => { if (token.endsWith("ies") && token.length > 3) { return `${token.slice(0, -3)}y`; } if (token.endsWith("s") && token.length > 3) return token.slice(0, -1); return token; }) .filter((token) => !genericActionTokens.has(token)); } function authorized( entry: RegisteredRoute, context: HousekeepingCapabilityContext, ): boolean { return ( satisfiesCapability(context, entry.domain.capability) && satisfiesCapability(context, entry.route.capability) ); } function routeForAudit( row: HousekeepingRecentAuditRow, routes: readonly RegisteredRoute[], context: HousekeepingCapabilityContext, ): RegisteredRoute | undefined { const domainRoutes = routes.filter( (entry) => entry.domain.id === row.domain && authorized(entry, context), ); const exact = domainRoutes.find((entry) => entry.route.id === row.target); if (exact) return exact; const actionTokens = new Set( normalizedTokens(row.action).filter((token) => token !== row.domain), ); let best: { entry: RegisteredRoute; score: number } | undefined; for (const entry of domainRoutes) { const score = normalizedTokens(entry.route.id) .filter((token) => token !== row.domain) .reduce((total, token) => total + (actionTokens.has(token) ? 1 : 0), 0); if (score > 0 && (!best || score > best.score)) best = { entry, score }; } return best?.entry; } function recentItem( row: HousekeepingRecentAuditRow, route: RegisteredRoute, ): HousekeepingRecentItem { return { routeId: route.route.id, canonicalHref: route.route.href, labelKey: route.route.labelKey, occurredAt: row.createdAt, source: row.action === "housekeeping.route.visit" ? "route-visit" : "audit", }; } export function deriveHousekeepingRecentWork( rows: readonly HousekeepingRecentAuditRow[], registry: HousekeepingRegistry, context: HousekeepingCapabilityContext, ): readonly HousekeepingRecentItem[] { const routes = registeredRoutes(registry); const sorted = [...rows].sort( (left, right) => Date.parse(right.createdAt) - Date.parse(left.createdAt), ); const seen = new Set(); const items: HousekeepingRecentItem[] = []; for (const row of sorted) { if (!completedOutcomes.has(row.outcome ?? "")) continue; if (!Number.isFinite(Date.parse(row.createdAt))) continue; const route = row.action === "housekeeping.route.visit" ? routes.find( (entry) => entry.route.id === row.target && authorized(entry, context), ) : routeForAudit(row, routes, context); if (!route || seen.has(route.route.id)) continue; seen.add(route.route.id); items.push(recentItem(row, route)); if (items.length === RECENT_WORK_POLICY.limit) break; } return items; } export function createHousekeepingRecentWorkService({ registry, loadRows, writeAudit, createCorrelationId: correlationIdFactory = createCorrelationId, }: HousekeepingRecentWorkDependencies) { return { async load( context: HousekeepingCapabilityContext, ): Promise> { const correlationId = correlationIdFactory(); try { const rows = await loadRows( context.actor.id, RECENT_WORK_POLICY.scanLimit, ); return ok( deriveHousekeepingRecentWork(rows, registry, context), correlationId, ); } catch { return fail( "INTERNAL", "errors.housekeeping.recent.read", correlationId, ); } }, async recordVisit( routeId: string, context: HousekeepingCapabilityContext, ): Promise> { const correlationId = correlationIdFactory(); const entry = registeredRoutes(registry).find( (candidate) => candidate.route.id === routeId, ); if (!entry) { return fail( "NOT_FOUND", "errors.housekeeping.recent.routeNotFound", correlationId, ); } if (!authorized(entry, context)) { return fail( "FORBIDDEN", "errors.housekeeping.forbidden", correlationId, ); } const occurredAt = new Date().toISOString(); try { await writeAudit({ userId: context.actor.id, action: "housekeeping.route.visit", target: entry.route.id, after: { canonicalHref: entry.route.href, labelKey: entry.route.labelKey, }, correlationId, outcome: "success", domain: entry.domain.id, }); return ok( { routeId: entry.route.id, canonicalHref: entry.route.href, labelKey: entry.route.labelKey, occurredAt, source: "route-visit", }, correlationId, ); } catch { return fail( "INTERNAL", "errors.housekeeping.recent.record", correlationId, ); } }, }; }