Revert "Merge pull request 'Complete Housekeeping migration and /ase cutover' (#52) from codex/housekeeping-complete into main"
CI / check (push) Successful in 27s
CI / release (push) Skipped
CI / deploy (push) Successful in 43s

This reverts commit 488b6e57c4, reversing
changes made to b506b4499a.
This commit is contained in:
Simo committed 2026-08-30 21:31:34 +02:00
1 parent 488b6e57c4
commit b1ddda66ff
802 files changed
+61370 -76659

No files matched your search

@@ -1,208 +0,0 @@
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";
import { anyCapability, ok } from "./contracts";
import { HousekeepingPageShell } from "./page/housekeeping-page-shell";
import { HousekeepingPageState } from "./page/housekeeping-page-state";
import type { HousekeepingPreferences } from "./preferences/schema";
import { Favorites } from "./shell/favorites";
import { HousekeepingShell } from "./shell/housekeeping-shell";
import { OperationalInbox } from "./shell/operational-inbox";
import { WidgetGrid } from "./shell/widget-grid";
vi.mock("@/actions/housekeeping-search", () => ({
executeHousekeepingSearch: vi.fn(),
}));
const capability = anyCapability("admin.users.view");
const preferences: HousekeepingPreferences = {
schemaVersion: 1,
pinnedRouteIds: ["people.users.list"],
pinnedCommandIds: [],
shortcutOrder: ["people.users.list"],
enabledOptionalWidgetIds: [],
widgetOrder: ["people.queue"],
};
const savePreferences = vi.fn(async (value: HousekeepingPreferences) =>
ok(value, "saved"),
);
const shellLabels = {
skipToContent: "Skip to content",
primaryNavigation: "Primary navigation",
contextualNavigation: "Context navigation",
command: "Search",
preview: "Preview",
backToSite: "Back to site",
operatorRank: "Rank 6",
commandDeck: {
placeholder: "Search",
navigation: "Navigation",
commands: "Commands",
entities: "Entities",
loading: "Loading",
empty: "Empty",
partial: "Partial",
close: "Close",
},
};
describe("housekeeping accessibility contract", () => {
it("renders the shell and page with labelled landmarks and a coherent heading hierarchy", () => {
const html = renderToStaticMarkup(
<HousekeepingShell
actor={{ id: 7, username: "Nora", rank: 6 }}
activeDomainId="operations"
domains={[
{
id: "operations",
href: "/ase/operations",
iconId: "inbox",
label: "Operations",
description: "Daily operations",
items: [],
},
]}
labels={shellLabels}
search={vi.fn()}
>
<HousekeepingPageShell title="Operations" description="Daily work">
<h2>Inbox</h2>
</HousekeepingPageShell>
</HousekeepingShell>,
);
expect(html).toContain('data-housekeeping-root="true"');
expect(html).toContain("<header");
expect(html).toContain('<nav aria-label="Primary navigation"');
expect(html).toContain('<nav aria-label="Context navigation"');
expect(html).toContain('<main id="housekeeping-content"');
expect(html).toContain('aria-labelledby="housekeeping-page-title"');
expect(html).toContain('<h1 id="housekeeping-page-title"');
expect(html.match(/aria-current="page"/g)).toHaveLength(1);
expect(html).toContain(">Rank 6</span>");
expect(html).toContain("focus-visible:not-sr-only");
});
it.each([
["loading", "status", "polite"],
["empty", "status", "polite"],
["partial", "status", "polite"],
["error", "alert", "assertive"],
["forbidden", "alert", "assertive"],
["ready", "status", "polite"],
] as const)(
"gives the %s page state explicit semantic announcements",
(state, role, live) => {
const pageState =
state === "partial" ? (
<HousekeepingPageState
state="partial"
partialLabel="Partial"
title={`${state} title`}
description={`${state} description`}
/>
) : (
<HousekeepingPageState
state={state}
title={`${state} title`}
description={`${state} description`}
/>
);
const html = renderToStaticMarkup(pageState);
expect(html).toContain(`data-page-state="${state}"`);
expect(html).toContain(`role="${role}"`);
expect(html).toContain(`aria-live="${live}"`);
},
);
it("provides keyboard reorder instructions for favorites and widgets", () => {
const favorites = renderToStaticMarkup(
<Favorites
preferences={preferences}
routes={[
{
id: "people.users.list",
labelKey: "pages.housekeeping.routes.people.users.list",
href: "/ase/people/users",
capability,
},
]}
savePreferences={savePreferences}
titles={{
"pages.housekeeping.routes.people.users.list": "Users",
}}
labels={{
title: "Favorites",
pin: "Pin",
unpin: "Unpin",
error: "Could not save",
reorderInstructions:
"Press Space, then use the arrow keys to reorder.",
}}
/>,
);
const widgets = renderToStaticMarkup(
<WidgetGrid
preferences={preferences}
result={{
widgets: [{ id: "people.queue", data: { count: 1 } }],
errors: [],
}}
savePreferences={savePreferences}
titles={{ "people.queue": "People queue" }}
labels={{
title: "Widgets",
partial: "Partial widgets",
error: "Could not save",
reorderInstructions:
"Press Space, then use the arrow keys to reorder.",
}}
/>,
);
expect(favorites).toContain(
'id="housekeeping-favorites-reorder-instructions"',
);
expect(favorites).toContain(
'aria-describedby="housekeeping-favorites-reorder-instructions"',
);
expect(favorites).toContain(">Users<");
expect(favorites).not.toContain(
">pages.housekeeping.routes.people.users.list<",
);
expect(widgets).toContain('id="housekeeping-widgets-reorder-instructions"');
expect(widgets).toContain(
'aria-describedby="housekeeping-widgets-reorder-instructions"',
);
expect(widgets).toContain(">People queue<");
});
it("announces partial provider failures without exposing internal identifiers", () => {
const html = renderToStaticMarkup(
<OperationalInbox
response={{
items: [],
errors: [{ sourceId: "internal.provider", code: "TIMEOUT" }],
correlationId: "private-correlation",
}}
labels={{
title: "Inbox",
domainFilter: "Domain",
stateFilter: "State",
priorityFilter: "Priority",
all: "All",
loading: "Loading",
empty: "Empty",
partial: "Some data is unavailable",
open: "Open",
}}
/>,
);
expect(html).toContain('role="alert"');
expect(html).toContain('aria-live="assertive"');
expect(html).not.toContain("internal.provider");
expect(html).not.toContain("TIMEOUT");
expect(html).not.toContain("private-correlation");
});
});
@@ -1,41 +0,0 @@
import { describe, expect, it } from "vitest";
import { authorizeHousekeeping } from "./authorization";
import { anyCapability, type HousekeepingCapabilityContext } from "./contracts";
function capabilityContext(
granted: readonly string[],
rank = 7,
): HousekeepingCapabilityContext {
return {
actor: { id: 42, username: "operator", rank },
isSuperAdmin: false,
has: (slug) => granted.includes(slug),
hasAny: (...slugs) => slugs.some((slug) => granted.includes(slug)),
hasAll: (...slugs) => slugs.every((slug) => granted.includes(slug)),
};
}
describe("authorizeHousekeeping", () => {
it("returns the authorized capability context", () => {
const context = capabilityContext(["admin.users.view"]);
expect(
authorizeHousekeeping(context, anyCapability("admin.users.view")),
).toMatchObject({ ok: true, data: context });
});
it("returns FORBIDDEN for a missing capability regardless of rank", () => {
const result = authorizeHousekeeping(
capabilityContext([], 999),
anyCapability("admin.users.view"),
);
expect(result).toMatchObject({
ok: false,
error: {
code: "FORBIDDEN",
messageKey: "errors.housekeeping.forbidden",
},
});
});
});
@@ -1,19 +0,0 @@
import { satisfiesCapability } from "./capability-context";
import type {
CapabilityRequirement,
HousekeepingCapabilityContext,
HousekeepingResult,
} from "./contracts";
import { fail, ok } from "./contracts";
import { createCorrelationId } from "./correlation";
export function authorizeHousekeeping(
context: HousekeepingCapabilityContext,
requirement: CapabilityRequirement,
): HousekeepingResult<HousekeepingCapabilityContext> {
const correlationId = createCorrelationId();
return satisfiesCapability(context, requirement)
? ok(context, correlationId)
: fail("FORBIDDEN", "errors.housekeeping.forbidden", correlationId);
}
@@ -1,164 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import type { AuditEntry, HousekeepingAuditWriter } from "@/lib/services/audit";
import {
runWithAuditIntent,
writeIntent,
writeOutcome,
} from "./audit-envelope";
const auditEntry: AuditEntry = {
userId: 7,
action: "maintenance.run",
target: "system",
correlationId: "corr-command-7",
domain: "system",
reason: "Scheduled maintenance",
};
function createWriter(): HousekeepingAuditWriter {
return { write: vi.fn().mockResolvedValue(undefined) };
}
describe("audit command envelope", () => {
it("writes an intent with the original correlation evidence", async () => {
const writer = createWriter();
await writeIntent(writer, auditEntry);
expect(writer.write).toHaveBeenCalledWith(
{ ...auditEntry, outcome: "intent" },
undefined,
);
});
it("writes denied and failure outcomes as durable evidence", async () => {
const writer = createWriter();
await writeOutcome(writer, auditEntry, "denied");
await writeOutcome(writer, auditEntry, "failure");
expect(writer.write).toHaveBeenNthCalledWith(
1,
{ ...auditEntry, outcome: "denied" },
undefined,
);
expect(writer.write).toHaveBeenNthCalledWith(
2,
{ ...auditEntry, outcome: "failure" },
undefined,
);
});
it("blocks the operation when intent persistence fails", async () => {
const writer: HousekeepingAuditWriter = {
write: vi.fn().mockRejectedValue(new Error("audit unavailable")),
};
const operation = vi.fn().mockResolvedValue("external operation result");
await expect(
runWithAuditIntent(writer, auditEntry, operation),
).rejects.toThrow("audit unavailable");
expect(operation).not.toHaveBeenCalled();
});
it("writes a success outcome after the operation completes", async () => {
const writer = createWriter();
await expect(
runWithAuditIntent(writer, auditEntry, () => Promise.resolve("done")),
).resolves.toBe("done");
expect(writer.write).toHaveBeenNthCalledWith(
1,
{ ...auditEntry, outcome: "intent" },
undefined,
);
expect(writer.write).toHaveBeenNthCalledWith(
2,
{ ...auditEntry, outcome: "success" },
undefined,
);
});
it("writes a failure outcome before rethrowing an operation failure", async () => {
const writer = createWriter();
await expect(
runWithAuditIntent(writer, auditEntry, () =>
Promise.reject(new Error("operation failed")),
),
).rejects.toThrow("operation failed");
expect(writer.write).toHaveBeenNthCalledWith(
2,
{ ...auditEntry, outcome: "failure" },
undefined,
);
});
it("preserves the original operation error when failure evidence rejects", async () => {
const original = new Error("operation failed");
const auditFailure = new Error("audit failure");
const writer: HousekeepingAuditWriter = {
write: vi
.fn()
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(auditFailure),
};
await expect(
runWithAuditIntent(writer, auditEntry, () => Promise.reject(original)),
).rejects.toBe(original);
expect(
(original as Error & { auditOutcomeError?: unknown }).auditOutcomeError,
).toBe(auditFailure);
});
it("throws a completed-operation error when success evidence rejects", async () => {
const auditFailure = new Error("audit failure");
const writer: HousekeepingAuditWriter = {
write: vi
.fn()
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(auditFailure),
};
await expect(
runWithAuditIntent(writer, auditEntry, () => Promise.resolve("done")),
).rejects.toMatchObject({
operationCompleted: true,
operationResult: "done",
auditOutcomeError: auditFailure,
});
});
it("preserves a frozen operation error when failure evidence rejects", async () => {
const original = Object.freeze(new Error("operation failed"));
const writer: HousekeepingAuditWriter = {
write: vi
.fn()
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(new Error("audit failure")),
};
await expect(
runWithAuditIntent(writer, auditEntry, () => Promise.reject(original)),
).rejects.toBe(original);
});
it("preserves a pathological rejection when failure evidence rejects", async () => {
const { proxy, revoke } = Proxy.revocable(
new Error("operation failed"),
{},
);
revoke();
const writer: HousekeepingAuditWriter = {
write: vi
.fn()
.mockResolvedValueOnce(undefined)
.mockRejectedValueOnce(new Error("audit failure")),
};
let rejected: unknown;
try {
await runWithAuditIntent(writer, auditEntry, () => Promise.reject(proxy));
} catch (error) {
rejected = error;
}
expect(rejected === proxy).toBe(true);
});
});
@@ -1,72 +0,0 @@
import type {
AuditEntry,
HousekeepingAuditTransaction,
HousekeepingAuditWriter,
} from "@/lib/services/audit";
type AuditOutcome = Exclude<NonNullable<AuditEntry["outcome"]>, "intent">;
export class AuditOutcomePersistenceError<T> extends Error {
readonly operationCompleted = true;
constructor(
readonly operationResult: T,
readonly auditOutcomeError: unknown,
) {
super("Operation completed but audit outcome could not be persisted");
this.name = "AuditOutcomePersistenceError";
}
}
export async function writeIntent(
writer: HousekeepingAuditWriter,
entry: AuditEntry,
transaction?: HousekeepingAuditTransaction,
): Promise<void> {
await writer.write({ ...entry, outcome: "intent" }, transaction);
}
export async function writeOutcome(
writer: HousekeepingAuditWriter,
entry: AuditEntry,
outcome: AuditOutcome,
transaction?: HousekeepingAuditTransaction,
): Promise<void> {
await writer.write({ ...entry, outcome }, transaction);
}
export async function runWithAuditIntent<T>(
writer: HousekeepingAuditWriter,
entry: AuditEntry,
operation: () => Promise<T>,
transaction?: HousekeepingAuditTransaction,
): Promise<T> {
await writeIntent(writer, entry, transaction);
let result: T;
try {
result = await operation();
} catch (error) {
try {
await writeOutcome(writer, entry, "failure", transaction);
} catch (auditOutcomeError) {
try {
if (error instanceof Error) {
Object.defineProperty(error, "auditOutcomeError", {
value: auditOutcomeError,
configurable: true,
});
}
} catch {
// Enrichment is best-effort; always preserve the original rejection.
}
}
throw error;
}
try {
await writeOutcome(writer, entry, "success", transaction);
} catch (auditOutcomeError) {
throw new AuditOutcomePersistenceError(result, auditOutcomeError);
}
return result;
}
@@ -1,96 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { z } from "zod";
vi.mock("@/lib/auth", () => ({ invalidateLoginCache: vi.fn() }));
vi.mock("@/lib/auth/password", () => ({ hashPassword: vi.fn() }));
import { housekeepingCommandRegistryReady } from "../../commands";
import { COMMUNITY_COMMAND_IDS } from "../../domains/people/commands/community-commands";
import { MODERATION_COMMAND_IDS } from "../../domains/people/commands/moderation-commands";
import { SUPPORT_COMMAND_IDS } from "../../domains/people/commands/support-commands";
import { USER_COMMAND_IDS } from "../../domains/people/commands/user-commands";
import { SYSTEM_COMMAND_IDS } from "../../domains/system/commands/system-commands";
import { anyCapability, ok } from "../contracts";
import {
defineHousekeepingCommands,
registerHousekeepingCommands,
} from "./bootstrap";
import {
getHousekeepingCommand,
type HousekeepingCommand,
registerHousekeepingCommand,
} from "./registry";
describe("housekeeping command bootstrap", () => {
it("preserves heterogeneous concrete input and output types", () => {
const stringCommand: HousekeepingCommand<
{ value: string },
{ length: number }
> = {
id: "system.bootstrap.string-shape",
owner: "system",
risk: "safe",
capability: anyCapability("admin.settings.view"),
input: z.object({ value: z.string() }),
requiresReason: false,
rateLimit: { attempts: 1, windowMs: 1_000 },
execute: async (context, input) =>
ok({ length: input.value.length }, context.correlationId),
};
const numberCommand: HousekeepingCommand<
{ count: number },
{ doubled: number }
> = {
id: "system.bootstrap.number-shape",
owner: "system",
risk: "safe",
capability: anyCapability("admin.settings.view"),
input: z.object({ count: z.number().int() }),
requiresReason: false,
rateLimit: { attempts: 2, windowMs: 2_000 },
execute: async (context, input) =>
ok({ doubled: input.count * 2 }, context.correlationId),
};
const commands = defineHousekeepingCommands(stringCommand, numberCommand);
const stringInput: z.input<(typeof commands)[0]["input"]> = {
value: "typed",
};
const numberInput: z.input<(typeof commands)[1]["input"]> = { count: 4 };
expect(commands[0].input.parse(stringInput)).toEqual({ value: "typed" });
expect(commands[1].input.parse(numberInput)).toEqual({ count: 4 });
expect(() => registerHousekeepingCommands(commands)).toThrow(
"command registry is sealed",
);
});
it("registers the complete current list and seals during module initialization", () => {
expect(housekeepingCommandRegistryReady).toBe(true);
expect(
SYSTEM_COMMAND_IDS.map((id) => getHousekeepingCommand(id)?.id),
).toEqual(SYSTEM_COMMAND_IDS);
expect(
USER_COMMAND_IDS.map((id) => getHousekeepingCommand(id)?.id),
).toEqual(USER_COMMAND_IDS);
expect(
COMMUNITY_COMMAND_IDS.map((id) => getHousekeepingCommand(id)?.id),
).toEqual(COMMUNITY_COMMAND_IDS);
expect(
SUPPORT_COMMAND_IDS.map((id) => getHousekeepingCommand(id)?.id),
).toEqual(SUPPORT_COMMAND_IDS);
expect(
MODERATION_COMMAND_IDS.map((id) => getHousekeepingCommand(id)?.id),
).toEqual(MODERATION_COMMAND_IDS);
expect(() =>
registerHousekeepingCommand({
id: "system.bootstrap.too-late",
owner: "system",
risk: "safe",
capability: anyCapability("admin.settings.view"),
input: z.object({}),
requiresReason: false,
rateLimit: { attempts: 1, windowMs: 1_000 },
execute: async (context) => ok(null, context.correlationId),
}),
).toThrow("command registry is sealed");
});
});
@@ -1,32 +0,0 @@
import "server-only";
import type { HousekeepingCommand } from "./registry";
import { registerHousekeepingCommand } from "./registry";
type PreserveHousekeepingCommand<Definition> =
Definition extends HousekeepingCommand<infer _Input, infer _Output>
? Definition
: never;
type HousekeepingCommandTuple<Commands extends readonly unknown[]> =
Commands & {
readonly [Index in keyof Commands]: PreserveHousekeepingCommand<
Commands[Index]
>;
};
export function defineHousekeepingCommands<
const Commands extends readonly unknown[],
>(...commands: HousekeepingCommandTuple<Commands>): Commands {
return commands;
}
export function registerHousekeepingCommands<
const Commands extends readonly unknown[],
>(commands: HousekeepingCommandTuple<Commands>): void {
for (const command of commands) {
registerHousekeepingCommand(
command as unknown as HousekeepingCommand<unknown, unknown>,
);
}
}
@@ -1,78 +0,0 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { anyCapability, ok } from "../contracts";
import { confirmHousekeepingCommand } from "./confirmation";
import type { HousekeepingCommand } from "./registry";
function command(
requiresReason: boolean,
risk: "safe" | "sensitive",
): HousekeepingCommand<Record<string, never>, null> {
return {
id: "system.confirmation.test",
owner: "system",
risk,
capability: anyCapability("admin.settings.edit"),
input: z.object({}),
requiresReason,
rateLimit: { attempts: 2, windowMs: 60_000 },
execute: async (context) => ok(null, context.correlationId),
};
}
describe("housekeeping command confirmation", () => {
it("rejects blank reasons when the registered command requires one", () => {
expect(
confirmHousekeepingCommand(
command(true, "sensitive"),
" \t ",
"corr-reason-missing",
),
).toEqual({
ok: false,
error: {
code: "VALIDATION",
messageKey: "errors.housekeeping.validation",
fieldErrors: { reason: ["errors.validation.required"] },
},
correlationId: "corr-reason-missing",
});
});
it("derives confirmation risk from the registered command and normalizes its reason", () => {
expect(
confirmHousekeepingCommand(
command(true, "sensitive"),
" Scheduled maintenance ",
"corr-confirm",
),
).toEqual({
ok: true,
data: {
commandId: "system.confirmation.test",
risk: "sensitive",
requiresReason: true,
reason: "Scheduled maintenance",
},
correlationId: "corr-confirm",
});
});
it("omits an empty optional reason without inventing confirmation metadata", () => {
const result = confirmHousekeepingCommand(
command(false, "safe"),
undefined,
"corr-safe",
);
expect(result).toEqual({
ok: true,
data: {
commandId: "system.confirmation.test",
risk: "safe",
requiresReason: false,
},
correlationId: "corr-safe",
});
});
});
@@ -1,32 +0,0 @@
import { fail, type HousekeepingResult, ok } from "../contracts";
import type { HousekeepingCommand } from "./registry";
export interface HousekeepingCommandConfirmation {
commandId: string;
risk: "safe" | "sensitive";
requiresReason: boolean;
reason?: string;
}
export function confirmHousekeepingCommand<I, O>(
command: HousekeepingCommand<I, O>,
reason: string | undefined,
correlationId: string,
): HousekeepingResult<HousekeepingCommandConfirmation> {
const normalizedReason = reason?.normalize("NFC").trim() || undefined;
if (command.requiresReason && !normalizedReason) {
return fail("VALIDATION", "errors.housekeeping.validation", correlationId, {
reason: ["errors.validation.required"],
});
}
return ok(
{
commandId: command.id,
risk: command.risk,
requiresReason: command.requiresReason,
...(normalizedReason === undefined ? {} : { reason: normalizedReason }),
},
correlationId,
);
}
File diff suppressed because it is too large. Load diff
@@ -1,514 +0,0 @@
import { isIP } from "node:net";
import { z } from "zod";
import type { AuditEntry, HousekeepingAuditWriter } from "@/lib/services/audit";
import { satisfiesCapability } from "../capability-context";
import {
fail,
type HousekeepingCapabilityContext,
type HousekeepingPartialCompletion,
type HousekeepingResult,
mapUnknownError,
} from "../contracts";
import { createCorrelationId } from "../correlation";
import { writeIntent, writeOutcome } from "./audit-envelope";
import { confirmHousekeepingCommand } from "./confirmation";
import {
getHousekeepingCommand,
type HousekeepingCommand,
type HousekeepingCommandContext,
isHousekeepingCommandId,
} from "./registry";
const normalizedCommandId = z
.string()
.transform((value) => value.normalize("NFC").trim())
.pipe(
z
.string()
.min(1)
.max(160)
.refine(isHousekeepingCommandId, "invalid command id"),
);
const normalizedReason = z
.string()
.transform((value) => value.normalize("NFC").trim())
.pipe(z.string().max(1000));
const commandRequestSchema = z.strictObject({
commandId: normalizedCommandId,
input: z.unknown(),
reason: normalizedReason.optional(),
});
const housekeepingErrorCodeSchema = z.enum([
"UNAUTHENTICATED",
"FORBIDDEN",
"VALIDATION",
"NOT_FOUND",
"CONFLICT",
"RATE_LIMITED",
"DEPENDENCY_UNAVAILABLE",
"TIMEOUT",
"INTERNAL",
]);
const housekeepingErrorSchema = z.strictObject({
code: housekeepingErrorCodeSchema,
messageKey: z.string().min(1).max(160),
fieldErrors: z.record(z.string(), z.array(z.string())).optional(),
});
const housekeepingResultSchema = z.discriminatedUnion("ok", [
z.strictObject({
ok: z.literal(true),
data: z.unknown(),
correlationId: z.string().min(1).max(160),
completion: z
.strictObject({
status: z.literal("partial"),
external: z.enum(["not-required", "completed", "failed"]),
audit: z.enum(["persisted", "unavailable"]),
})
.optional(),
}),
z.strictObject({
ok: z.literal(false),
error: housekeepingErrorSchema,
correlationId: z.string().min(1).max(160),
}),
]);
export interface HousekeepingCommandRequest {
commandId: string;
input: unknown;
reason?: string;
}
export interface HousekeepingCommandDependencies {
context: HousekeepingCapabilityContext;
ipAddress: string;
audit: HousekeepingAuditWriter;
rateLimit(key: string, attempts: number, windowMs: number): Promise<boolean>;
}
export async function dispatchHousekeepingCommand(
request: unknown,
dependencies: HousekeepingCommandDependencies,
): Promise<HousekeepingResult<unknown>> {
const correlationId = createCorrelationId();
const ipAddress = canonicalizeServerIp(dependencies.ipAddress);
if (ipAddress === undefined) {
return persistReturnedOutcome(
dependencies.audit,
createDispatchAuditEntry(
"server-context",
dependencies.context,
correlationId,
),
"failure",
mapUnknownError(new Error("invalid server IP"), correlationId),
);
}
if (!isPlainRecord(request)) {
return persistMalformedRequestOutcome(
dependencies,
ipAddress,
correlationId,
);
}
let parsedRequest: ReturnType<typeof commandRequestSchema.safeParse>;
try {
parsedRequest = commandRequestSchema.safeParse(request);
} catch (error) {
return persistReturnedOutcome(
dependencies.audit,
createDispatchAuditEntry(
"request-envelope",
dependencies.context,
correlationId,
ipAddress,
),
"failure",
mapUnknownError(error, correlationId),
);
}
if (!parsedRequest.success) {
return persistMalformedRequestOutcome(
dependencies,
ipAddress,
correlationId,
);
}
const commandRequest = parsedRequest.data;
const command = getHousekeepingCommand(commandRequest.commandId);
if (!command) {
const result = fail(
"NOT_FOUND",
"errors.housekeeping.notFound",
correlationId,
);
return persistReturnedOutcome(
dependencies.audit,
createUnknownCommandAuditEntry(
commandRequest.commandId,
dependencies.context,
ipAddress,
correlationId,
),
"denied",
result,
);
}
const auditEntry = createAuditEntry(
command,
dependencies.context,
ipAddress,
correlationId,
commandRequest.reason || undefined,
);
let authorized: boolean;
try {
authorized = satisfiesCapability(dependencies.context, command.capability);
} catch (error) {
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
"failure",
mapUnknownError(error, correlationId),
);
}
if (!authorized) {
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
"denied",
fail("FORBIDDEN", "errors.housekeeping.forbidden", correlationId),
);
}
let parsedInput: ReturnType<typeof command.input.safeParse>;
try {
parsedInput = command.input.safeParse(commandRequest.input);
} catch (error) {
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
"failure",
mapUnknownError(error, correlationId),
);
}
if (!parsedInput.success) {
const fieldErrors: Record<string, readonly string[]> = {};
for (const issue of parsedInput.error.issues) {
const field = String(issue.path[0] ?? "input");
fieldErrors[field] = ["errors.validation.invalid"];
}
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
"denied",
fail(
"VALIDATION",
"errors.housekeeping.validation",
correlationId,
fieldErrors,
),
);
}
let confirmation: HousekeepingResult<unknown>;
try {
confirmation = confirmHousekeepingCommand(
command,
commandRequest.reason || undefined,
correlationId,
);
} catch (error) {
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
"failure",
mapUnknownError(error, correlationId),
);
}
if (!confirmation.ok) {
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
"denied",
confirmation,
);
}
let allowed: boolean;
try {
allowed = await dependencies.rateLimit(
createRateLimitKey(command.id, dependencies.context, ipAddress),
command.rateLimit.attempts,
command.rateLimit.windowMs,
);
} catch (error) {
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
"failure",
mapUnknownError(error, correlationId),
);
}
if (!allowed) {
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
"denied",
fail("RATE_LIMITED", "errors.housekeeping.rateLimited", correlationId),
);
}
if (command.risk === "sensitive") {
try {
await writeIntent(dependencies.audit, auditEntry);
} catch (error) {
return mapUnknownError(error, correlationId);
}
}
const commandContext: HousekeepingCommandContext = {
capability: dependencies.context,
correlationId,
ipAddress,
};
let correlatedResult: HousekeepingResult<unknown>;
try {
const rawResult = await command.execute(commandContext, parsedInput.data);
correlatedResult = validateAndCorrelateResult(rawResult, correlationId);
} catch (error) {
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
"failure",
mapUnknownError(error, correlationId),
);
}
if (!correlatedResult.ok) {
return persistReturnedOutcome(
dependencies.audit,
auditEntry,
outcomeForFailure(correlatedResult),
correlatedResult,
);
}
if (correlatedResult.completion?.status === "partial") {
return persistPartialCompletionOutcome(
dependencies.audit,
auditEntry,
correlatedResult,
);
}
return persistSuccessfulOutcome(
dependencies.audit,
auditEntry,
correlatedResult,
);
}
function createAuditEntry(
command: HousekeepingCommand<unknown, unknown>,
context: HousekeepingCapabilityContext,
ipAddress: string,
correlationId: string,
reason: string | undefined,
): AuditEntry {
return {
userId: context.actor.id,
action: command.id,
target: command.owner,
correlationId,
domain: command.owner,
...(reason === undefined ? {} : { reason }),
ipAddress,
};
}
function createDispatchAuditEntry(
target: "request-envelope" | "server-context",
context: HousekeepingCapabilityContext,
correlationId: string,
ipAddress?: string,
): AuditEntry {
return {
userId: context.actor.id,
action: "housekeeping.command.dispatch",
target,
correlationId,
...(ipAddress === undefined ? {} : { ipAddress }),
};
}
function createUnknownCommandAuditEntry(
commandId: string,
context: HousekeepingCapabilityContext,
ipAddress: string,
correlationId: string,
): AuditEntry {
return {
userId: context.actor.id,
action: "housekeeping.command.dispatch",
target: commandId,
correlationId,
ipAddress,
};
}
function createRateLimitKey(
commandId: string,
context: HousekeepingCapabilityContext,
ipAddress: string,
): string {
return `housekeeping-command:${context.actor.id}:${ipAddress}:${commandId}`;
}
function canonicalizeServerIp(value: unknown): string | undefined {
if (typeof value !== "string") return undefined;
const candidate = value.trim();
const version = isIP(candidate);
if (version === 4) {
return candidate
.split(".")
.map((part) => String(Number(part)))
.join(".");
}
if (version === 6) {
try {
return new URL(`http://[${candidate}]/`).hostname.slice(1, -1);
} catch {
return undefined;
}
}
return undefined;
}
function isPlainRecord(value: unknown): value is Record<string, unknown> {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return false;
}
try {
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
} catch {
return false;
}
}
function validateAndCorrelateResult(
value: unknown,
correlationId: string,
): HousekeepingResult<unknown> {
if (!isPlainRecord(value) || !Object.hasOwn(value, "ok")) {
throw new Error("invalid housekeeping command result");
}
if (value.ok === true && !Object.hasOwn(value, "data")) {
throw new Error("invalid housekeeping command result");
}
if (value.ok === false && !Object.hasOwn(value, "error")) {
throw new Error("invalid housekeeping command result");
}
const parsed = housekeepingResultSchema.safeParse(value);
if (!parsed.success) {
throw new Error("invalid housekeeping command result");
}
return { ...parsed.data, correlationId };
}
function outcomeForFailure(
result: Extract<HousekeepingResult<unknown>, { ok: false }>,
): "failure" | "denied" {
return result.error.code === "FORBIDDEN" ||
result.error.code === "VALIDATION" ||
result.error.code === "RATE_LIMITED"
? "denied"
: "failure";
}
function persistMalformedRequestOutcome(
dependencies: HousekeepingCommandDependencies,
ipAddress: string,
correlationId: string,
): Promise<HousekeepingResult<never>> {
return persistReturnedOutcome(
dependencies.audit,
createDispatchAuditEntry(
"request-envelope",
dependencies.context,
correlationId,
ipAddress,
),
"denied",
fail("VALIDATION", "errors.housekeeping.validation", correlationId),
);
}
async function persistReturnedOutcome<T>(
writer: HousekeepingAuditWriter,
entry: AuditEntry,
outcome: "failure" | "denied",
result: HousekeepingResult<T>,
): Promise<HousekeepingResult<T>> {
try {
await writeOutcome(writer, entry, outcome);
} catch {
// The command/preflight failure remains authoritative if evidence is down.
}
return result;
}
async function persistSuccessfulOutcome<T>(
writer: HousekeepingAuditWriter,
entry: AuditEntry,
result: Extract<HousekeepingResult<T>, { ok: true }>,
): Promise<HousekeepingResult<T>> {
try {
await writeOutcome(writer, entry, "success");
return result;
} catch {
let audit: HousekeepingPartialCompletion["audit"] = "persisted";
try {
await writeOutcome(writer, entry, "partial");
} catch {
audit = "unavailable";
}
return withPartialCompletion(result, {
status: "partial",
external: result.completion?.external ?? "not-required",
audit,
});
}
}
async function persistPartialCompletionOutcome<T>(
writer: HousekeepingAuditWriter,
entry: AuditEntry,
result: Extract<HousekeepingResult<T>, { ok: true }>,
): Promise<HousekeepingResult<T>> {
try {
await writeOutcome(writer, entry, "partial");
return result;
} catch {
return withPartialCompletion(result, {
...(result.completion ?? {
status: "partial",
external: "not-required",
audit: "unavailable",
}),
audit: "unavailable",
});
}
}
function withPartialCompletion<T>(
result: Extract<HousekeepingResult<T>, { ok: true }>,
completion: HousekeepingPartialCompletion,
): HousekeepingResult<T> {
return { ...result, completion };
}
@@ -1,886 +0,0 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { anyCapability, ok } from "../contracts";
import {
getHousekeepingCommand,
type HousekeepingCommand,
registerHousekeepingCommand,
sealHousekeepingCommandRegistry,
UnsupportedHousekeepingCommandSchemaError,
} from "./registry";
function command(
id: string,
owner: "people" | "system" = "people",
): HousekeepingCommand<{ id: number }, { id: number }> {
return {
id,
owner,
risk: "safe",
capability: anyCapability("admin.users.view"),
input: z.object({ id: z.number().int().positive() }),
requiresReason: false,
rateLimit: { attempts: 4, windowMs: 30_000 },
execute: async (context, input) => ok(input, context.correlationId),
};
}
type DescriptorReader = (
target: object,
property: PropertyKey,
) => PropertyDescriptor | undefined;
const descriptorReaders = [
[
"Object.getOwnPropertyDescriptor",
"object-descriptor",
Object.getOwnPropertyDescriptor,
],
[
"Object.getOwnPropertyDescriptors",
"object-descriptors",
(target: object, property: PropertyKey) =>
Reflect.get(Object.getOwnPropertyDescriptors(target), property) as
| PropertyDescriptor
| undefined,
],
[
"Reflect.getOwnPropertyDescriptor",
"reflect-descriptor",
Reflect.getOwnPropertyDescriptor,
],
] as const satisfies readonly (readonly [string, string, DescriptorReader])[];
function readDescriptorValue(
target: object,
property: PropertyKey,
readDescriptor: DescriptorReader,
): unknown {
const descriptor = readDescriptor(target, property);
if (!descriptor) throw new Error(`missing descriptor: ${String(property)}`);
if ("value" in descriptor) return descriptor.value;
if (descriptor.get) return Reflect.apply(descriptor.get, target, []);
return undefined;
}
function attemptMutation(mutate: () => void): void {
try {
mutate();
} catch {
// Readonly public views may reject the mutation directly.
}
}
function mutationWasRejected(mutate: () => void): boolean {
try {
mutate();
return false;
} catch {
return true;
}
}
const unsafeSchemaCallbackMessage =
"callback-bearing schema arguments are not supported";
const callbackOptionMethodNames = [
"parse",
"safeParse",
"parseAsync",
"safeParseAsync",
"spa",
"encode",
"decode",
"encodeAsync",
"decodeAsync",
"safeEncode",
"safeDecode",
"safeEncodeAsync",
"safeDecodeAsync",
] as const;
function attemptMinimumMutation(instance: unknown): void {
if (typeof instance !== "object" || instance === null) return;
const internal = Reflect.get(instance, "_zod") as
| { def?: { minimum?: number } }
| undefined;
if (typeof internal?.def?.minimum !== "number") return;
attemptMutation(() => {
if (internal.def) internal.def.minimum = 0;
});
}
describe("housekeeping command registry", () => {
it("returns the validated snapshot registered under its global ID", () => {
const registered = command("people.registry.lookup");
registerHousekeepingCommand(registered);
expect(getHousekeepingCommand(registered.id)).toMatchObject({
id: "people.registry.lookup",
owner: "people",
risk: "safe",
requiresReason: false,
rateLimit: { attempts: 4, windowMs: 30_000 },
});
expect(getHousekeepingCommand(registered.id)).not.toBe(registered);
});
it("rejects a duplicate global command ID before it can shadow its owner", () => {
registerHousekeepingCommand(command("people.registry.duplicate"));
expect(() =>
registerHousekeepingCommand(command("people.registry.duplicate")),
).toThrow("duplicate command id: people.registry.duplicate");
});
it("rejects a command whose namespace disagrees with its declared owner", () => {
expect(() =>
registerHousekeepingCommand(
command("people.registry.owner-mismatch", "system"),
),
).toThrow("command owner mismatch: people.registry.owner-mismatch");
});
it("stores a frozen snapshot that resists mutation through the original definition", () => {
const original = command("people.registry.immutable");
const callerOwnedInput = original.input;
registerHousekeepingCommand(original);
const registered = getHousekeepingCommand(original.id);
if (!registered) throw new Error("registered command missing");
const registeredInput = registered.input;
const registeredExecute = registered.execute;
(original as { risk: "safe" | "sensitive" }).risk = "sensitive";
(original as { owner: "people" | "system" }).owner = "system";
(original as { input: z.ZodType<{ id: number }> }).input = z.object({
id: z.literal(999),
});
(original.capability.slugs as string[]).push("admin.settings.edit");
(original.rateLimit as { attempts: number }).attempts = 999;
(
original as {
execute: HousekeepingCommand<{ id: number }, { id: number }>["execute"];
}
).execute = async (context) => ok({ id: 999 }, context.correlationId);
expect(registered).toMatchObject({
risk: "safe",
owner: "people",
capability: { slugs: ["admin.users.view"] },
rateLimit: { attempts: 4, windowMs: 30_000 },
});
expect(registered.input).toBe(registeredInput);
expect(registered.input).not.toBe(callerOwnedInput);
expect(Object.isFrozen(callerOwnedInput)).toBe(false);
expect(registered.execute).toBe(registeredExecute);
expect(Object.isFrozen(registered)).toBe(true);
expect(Object.isFrozen(registered.input)).toBe(true);
expect(Object.isFrozen(registered.capability)).toBe(true);
expect(Object.isFrozen(registered.capability.slugs)).toBe(true);
expect(Object.isFrozen(registered.rateLimit)).toBe(true);
});
it.each([
["risk", { risk: "dangerous" }],
["requiresReason", { requiresReason: "yes" }],
[
"capability mode",
{ capability: { mode: "none", slugs: ["admin.users.view"] } },
],
["empty capability", { capability: { mode: "any", slugs: [] } }],
[
"unknown capability",
{ capability: { mode: "any", slugs: ["forged.permission"] } },
],
["execute", { execute: null }],
[
"Zod schema",
{ input: { safeParse: () => ({ success: true, data: {} }) } },
],
["normalized ID", { id: " people.registry.invalid-id " }],
] as const)("rejects an invalid %s definition", (label, override) => {
const invalid = {
...command(
`people.registry.invalid-${label.toLowerCase().replaceAll(" ", "-")}`,
),
...override,
} as unknown as HousekeepingCommand<{ id: number }, { id: number }>;
expect(() => registerHousekeepingCommand(invalid)).toThrow();
});
it("keeps registered validation unchanged after original shape and child mutation", () => {
const child = z.string().min(3);
const input = z.object({ value: child, guard: child });
registerHousekeepingCommand({
...command("people.registry.deep-validation"),
input,
execute: async (context, value) =>
ok({ id: value.value.length }, context.correlationId),
} as HousekeepingCommand<{ value: string; guard: string }, { id: number }>);
const registered = getHousekeepingCommand(
"people.registry.deep-validation",
);
if (!registered) throw new Error("registered command missing");
(input.def as { shape: { value: z.ZodType } }).shape.value = z.number();
const minCheck = (child.def as { checks: unknown[] }).checks[0] as {
_zod: { def: { minimum: number } };
};
minCheck._zod.def.minimum = 0;
expect(input.safeParse({ value: 4, guard: "a" }).success).toBe(true);
expect(
registered.input.safeParse({ value: "abcd", guard: "ab" }).success,
).toBe(false);
expect(
registered.input.safeParse({ value: "abcd", guard: "abcd" }).success,
).toBe(true);
expect(
registered.input.safeParse({ value: 4, guard: "abcd" }).success,
).toBe(false);
});
it("snapshots caller-owned lazy targets across supported container schemas", () => {
let lazyChild: z.ZodType = z.string().min(2);
let defaultValue = "registered-default";
const input = z.object({
choice: z.union([z.lazy(() => lazyChild), z.number().int()]),
optional: z.lazy(() => lazyChild).optional(),
defaulted: z.string().default(() => defaultValue),
list: z.array(z.lazy(() => lazyChild)),
lookup: z.record(
z.string(),
z.lazy(() => lazyChild),
),
});
registerHousekeepingCommand({
...command("people.registry.lazy-containers"),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(
"people.registry.lazy-containers",
);
if (!registered) throw new Error("registered command missing");
lazyChild = z.boolean();
defaultValue = "caller-mutated-default";
const parsed = registered.input.safeParse({
choice: "ok",
list: ["one"],
lookup: { key: "two" },
});
expect(parsed).toMatchObject({
success: true,
data: { defaulted: "registered-default" },
});
expect(
registered.input.safeParse({
choice: true,
optional: true,
list: [true],
lookup: { key: true },
}).success,
).toBe(false);
});
it("snapshots recursive lazy back-edges with cycle safety", () => {
type TreeNode = { name: string; children: TreeNode[] };
let nameSchema: z.ZodType = z.string().min(2);
let recursiveSchema: z.ZodType<TreeNode>;
recursiveSchema = z.object({
name: z.lazy(() => nameSchema),
children: z.array(z.lazy(() => recursiveSchema)),
}) as z.ZodType<TreeNode>;
registerHousekeepingCommand({
...command("people.registry.recursive-lazy"),
input: recursiveSchema,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<TreeNode, { id: number }>);
const registered = getHousekeepingCommand("people.registry.recursive-lazy");
if (!registered) throw new Error("registered command missing");
nameSchema = z.number();
recursiveSchema = z.object({
name: z.number(),
children: z.array(z.unknown()),
}) as unknown as z.ZodType<TreeNode>;
expect(
registered.input.safeParse({
name: "root",
children: [{ name: "leaf", children: [] }],
}).success,
).toBe(true);
expect(
registered.input.safeParse({
name: "root",
children: [{ name: 7, children: [] }],
}).success,
).toBe(false);
});
it.each([
["refine", "custom", z.string().refine((value) => value === "allowed")],
["super-refine", "custom", z.string().superRefine(() => undefined)],
[
"preprocess",
"transform",
z.preprocess((value) => String(value), z.string()),
],
["transform", "transform", z.string().transform((value) => value.length)],
["async-refine", "custom", z.string().refine(async () => true)],
] as const)(
"rejects unsupported executable validation schema %s",
(suffix, schemaKind, input) => {
const id = `people.registry.unsupported-${suffix}`;
let thrown: unknown;
try {
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<unknown, { id: number }>);
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(UnsupportedHousekeepingCommandSchemaError);
expect(thrown).toMatchObject({
name: "UnsupportedHousekeepingCommandSchemaError",
code: "UNSUPPORTED_COMMAND_INPUT_SCHEMA",
commandId: id,
schemaKind,
});
expect(getHousekeepingCommand(id)).toBeUndefined();
},
);
it("rejects a lazy edge that cannot be resolved at registration", () => {
const id = "people.registry.unresolvable-lazy";
let thrown: unknown;
try {
registerHousekeepingCommand({
...command(id),
input: z.lazy(() => {
throw new Error("caller lazy secret");
}),
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<unknown, { id: number }>);
} catch (error) {
thrown = error;
}
expect(thrown).toBeInstanceOf(UnsupportedHousekeepingCommandSchemaError);
expect(thrown).toMatchObject({
name: "UnsupportedHousekeepingCommandSchemaError",
code: "UNSUPPORTED_COMMAND_INPUT_SCHEMA",
commandId: id,
schemaKind: "lazy",
});
expect(String(thrown)).not.toContain("caller lazy secret");
});
it.each(descriptorReaders)(
"keeps private validation unchanged after nested mutation through %s",
(_api, suffix, readDescriptor) => {
const input = z.object({ value: z.string().min(3) });
const id = `people.registry.${suffix}`;
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
const definition = readDescriptorValue(
registered.input,
"def",
readDescriptor,
) as object;
const shape = readDescriptorValue(
definition,
"shape",
readDescriptor,
) as { value: z.ZodType };
const child = shape.value;
const childDefinition = readDescriptorValue(
child,
"def",
readDescriptor,
) as object;
const checks = readDescriptorValue(
childDefinition,
"checks",
readDescriptor,
) as readonly object[];
const internal = readDescriptorValue(
checks[0] as object,
"_zod",
readDescriptor,
) as object;
const checkDefinition = readDescriptorValue(
internal,
"def",
readDescriptor,
) as { minimum: number };
attemptMutation(() => {
shape.value = z.number();
});
attemptMutation(() => {
checkDefinition.minimum = 0;
});
expect(registered.input.safeParse({ value: "ab" }).success).toBe(false);
expect(registered.input.safeParse({ value: "abcd" }).success).toBe(true);
expect(registered.input.safeParse({ value: 7 }).success).toBe(false);
},
);
it("keeps own-key and symbol iteration detached from private checks", () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.symbol-iteration";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
const publicInput = registered.input as z.ZodObject<{
value: z.ZodString;
}>;
const shape = publicInput.shape;
expect(Reflect.ownKeys(publicInput.def)).toContain("shape");
expect(Object.keys(shape)).toEqual(["value"]);
const child = Object.entries(shape)[0]?.[1];
if (!child) throw new Error("public child schema missing");
const checks = child.def.checks ?? [];
const spreadChecks = [...checks];
const iterator = checks[Symbol.iterator]();
const iteratedCheck = iterator.next().value;
if (!iteratedCheck) throw new Error("public check missing");
expect(spreadChecks[0]).toBe(iteratedCheck);
attemptMutation(() => {
(
iteratedCheck as unknown as {
_zod: { def: { minimum: number } };
}
)._zod.def.minimum = 0;
});
expect(registered.input.safeParse({ value: "ab" }).success).toBe(false);
expect(registered.input.safeParse({ value: "abcd" }).success).toBe(true);
});
it("keeps prototype methods operational without passing the private schema to callbacks", async () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.prototype-methods";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
const publicInput = registered.input as z.ZodObject<{
value: z.ZodString;
}>;
const publicPrototype = Object.getPrototypeOf(publicInput);
expect(publicPrototype).toBe(Reflect.getPrototypeOf(publicInput));
const prototypeMutation = Symbol("prototype-mutation");
Object.defineProperty(publicPrototype, prototypeMutation, {
configurable: true,
value(this: z.ZodObject<{ value: z.ZodString }>): unknown {
attemptMutation(() => {
this.def.shape.value = z.number() as never;
});
return this.def;
},
});
try {
(
publicInput as typeof publicInput & {
[prototypeMutation](): unknown;
}
)[prototypeMutation]();
} finally {
Reflect.deleteProperty(publicPrototype, prototypeMutation);
}
expect(publicInput.safeParse({ value: "abcd" }).success).toBe(true);
expect(publicInput.safeParse({ value: 7 }).success).toBe(false);
let appliedSchema: z.ZodType | undefined;
const applied = publicInput.apply((schema) => {
appliedSchema = schema;
return schema;
});
expect(appliedSchema).toBe(publicInput);
expect(applied).toBe(publicInput);
let externallyRegistered: z.ZodType | undefined;
const externalRegistry = {
add(schema: z.ZodType): void {
externallyRegistered = schema;
},
};
expect(
publicInput.register(externalRegistry as never, undefined as never),
).toBe(publicInput);
expect(externallyRegistered).toBe(publicInput);
const partial = publicInput.partial();
const picked = publicInput.pick({ value: true });
expect(partial.safeParse({}).success).toBe(true);
expect(picked.safeParse({ value: "abcd" }).success).toBe(true);
expect((await publicInput.safeParseAsync({ value: "ab" })).success).toBe(
false,
);
expect((await publicInput.safeParseAsync({ value: "abcd" })).success).toBe(
true,
);
});
it.each(callbackOptionMethodNames)(
"rejects callback-bearing %s options before private issue nodes are exposed",
async (methodName) => {
const input = z.object({ value: z.string().min(3) });
const id = `people.registry.callback-${methodName.toLowerCase()}`;
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
let callbackCalls = 0;
let capturedInstance: unknown;
const errorCallback = (issue: unknown): string => {
callbackCalls += 1;
capturedInstance = Reflect.get(issue as object, "inst");
attemptMinimumMutation(capturedInstance);
return "forged validation error";
};
const method = Reflect.get(registered.input, methodName);
if (typeof method !== "function") {
throw new Error(`public schema method missing: ${methodName}`);
}
const attack = Promise.resolve().then(() =>
Reflect.apply(method, registered.input, [
{ value: "x" },
{ error: errorCallback },
]),
);
await expect(attack).rejects.toThrow(unsafeSchemaCallbackMessage);
expect(callbackCalls).toBe(0);
expect(capturedInstance).toBeUndefined();
expect(registered.input.safeParse({ value: "x" }).success).toBe(false);
expect(registered.input.safeParse({ value: "valid" }).success).toBe(true);
},
);
it("rejects JSON-schema overrides before private schema or check nodes are exposed", () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.callback-json-schema";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
let callbackCalls = 0;
let capturedSchema: unknown;
let attackError: unknown;
try {
registered.input.toJSONSchema({
override: ({ zodSchema }) => {
callbackCalls += 1;
capturedSchema = zodSchema;
const checks = (zodSchema._zod.def as { checks?: unknown[] }).checks;
if (checks?.[0]) attemptMinimumMutation(checks[0]);
},
});
} catch (error) {
attackError = error;
}
expect({
attackError:
attackError instanceof TypeError ? attackError.message : undefined,
callbackCalls,
capturedSchema,
shortValueAccepted: registered.input.safeParse({ value: "x" }).success,
validValueAccepted: registered.input.safeParse({ value: "valid" })
.success,
}).toEqual({
attackError: unsafeSchemaCallbackMessage,
callbackCalls: 0,
capturedSchema: undefined,
shortValueAccepted: false,
validValueAccepted: true,
});
});
it("rejects an error callback concealed behind an option Proxy", () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.callback-proxy-options";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
let callbackCalls = 0;
let capturedInstance: unknown;
const errorCallback = (issue: unknown): string => {
callbackCalls += 1;
capturedInstance = Reflect.get(issue as object, "inst");
attemptMinimumMutation(capturedInstance);
return "forged validation error";
};
const concealedOptions = new Proxy(
{},
{
get: (_target, property) =>
property === "error" ? errorCallback : undefined,
getOwnPropertyDescriptor: () => undefined,
ownKeys: () => [],
},
);
let attackError: unknown;
try {
registered.input.safeParse({ value: "x" }, concealedOptions as never);
} catch (error) {
attackError = error;
}
expect({
attackError:
attackError instanceof TypeError ? attackError.message : undefined,
callbackCalls,
capturedInstance,
shortValueAccepted: registered.input.safeParse({ value: "x" }).success,
validValueAccepted: registered.input.safeParse({ value: "valid" })
.success,
}).toEqual({
attackError: unsafeSchemaCallbackMessage,
callbackCalls: 0,
capturedInstance: undefined,
shortValueAccepted: false,
validValueAccepted: true,
});
});
it("keeps callback-free sync and async parsing available", async () => {
const input = z.object({ value: z.string().min(3) });
const id = "people.registry.callback-free-parse";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
expect(
registered.input.parse({ value: "valid" }, { jitless: true }),
).toEqual({ value: "valid" });
expect(
registered.input.safeParse({ value: "valid" }, { reportInput: true }),
).toMatchObject({ success: true, data: { value: "valid" } });
await expect(
registered.input.parseAsync({ value: "valid" }, { jitless: true }),
).resolves.toEqual({ value: "valid" });
await expect(
registered.input.safeParseAsync({ value: "x" }, { reportInput: true }),
).resolves.toMatchObject({ success: false });
});
it("exposes detached Map, Set, and Date values as behaviorally readonly views", () => {
const sourceMap = new Map<string, { count: number }>([
["registered", { count: 1 }],
]);
const sourceSet = new Set(["registered"]);
const sourceDate = new Date("2026-08-26T12:34:56.000Z");
const input = z.object({ value: z.string() });
Object.assign(input.def, {
exposedMap: sourceMap,
exposedSet: sourceSet,
exposedDate: sourceDate,
});
const id = "people.registry.readonly-builtins";
registerHousekeepingCommand({
...command(id),
input,
execute: async (context) => ok({ id: 1 }, context.correlationId),
} as HousekeepingCommand<z.output<typeof input>, { id: number }>);
const registered = getHousekeepingCommand(id);
if (!registered) throw new Error("registered command missing");
const publicDefinition = registered.input
.def as typeof registered.input.def & {
exposedMap: ReadonlyMap<string, { count: number }>;
exposedSet: ReadonlySet<string>;
exposedDate: Date;
};
const publicMap = publicDefinition.exposedMap;
const publicSet = publicDefinition.exposedSet;
const publicDate = publicDefinition.exposedDate;
sourceMap.set("caller-late", { count: 2 });
sourceSet.add("caller-late");
sourceDate.setTime(0);
const mapForEachReceivers: unknown[] = [];
const mapEntries: Array<[string, number]> = [];
publicMap.forEach((value, key, collection) => {
mapEntries.push([key, value.count]);
mapForEachReceivers.push(collection);
});
const setForEachReceivers: unknown[] = [];
const setEntries: string[] = [];
publicSet.forEach((value, duplicate, collection) => {
expect(duplicate).toBe(value);
setEntries.push(value);
setForEachReceivers.push(collection);
});
const dateIsoBefore = publicDate.toISOString();
const mapEscape = Symbol("map-escape");
const setEscape = Symbol("set-escape");
const dateEscape = Symbol("date-escape");
Object.defineProperty(Map.prototype, mapEscape, {
configurable: true,
value(this: Map<unknown, unknown>): Map<unknown, unknown> {
return this;
},
});
Object.defineProperty(Set.prototype, setEscape, {
configurable: true,
value(this: Set<unknown>): Set<unknown> {
return this;
},
});
Object.defineProperty(Date.prototype, dateEscape, {
configurable: true,
value(this: Date): Date {
return this;
},
});
let escapedMap: unknown;
let escapedSet: unknown;
let escapedDate: unknown;
try {
const mapMethod = Reflect.get(publicMap, mapEscape);
const setMethod = Reflect.get(publicSet, setEscape);
const dateMethod = Reflect.get(publicDate, dateEscape);
if (
typeof mapMethod !== "function" ||
typeof setMethod !== "function" ||
typeof dateMethod !== "function"
) {
throw new Error("dynamic builtin method missing");
}
escapedMap = Reflect.apply(mapMethod, publicMap, []);
escapedSet = Reflect.apply(setMethod, publicSet, []);
escapedDate = Reflect.apply(dateMethod, publicDate, []);
} finally {
Reflect.deleteProperty(Map.prototype, mapEscape);
Reflect.deleteProperty(Set.prototype, setEscape);
Reflect.deleteProperty(Date.prototype, dateEscape);
}
const escapeMutationResults = [
mutationWasRejected(() =>
(escapedMap as Map<string, { count: number }>).set("escaped-forged", {
count: 11,
}),
),
mutationWasRejected(() =>
(escapedSet as Set<string>).add("escaped-forged"),
),
mutationWasRejected(() => (escapedDate as Date).setTime(0)),
];
const mapMutationResults = [
mutationWasRejected(() =>
(publicMap as Map<string, { count: number }>).set("forged", {
count: 9,
}),
),
mutationWasRejected(() =>
(publicMap as Map<string, { count: number }>).delete("registered"),
),
mutationWasRejected(() =>
(publicMap as Map<string, { count: number }>).clear(),
),
mutationWasRejected(() =>
Map.prototype.set.call(
publicMap as Map<string, { count: number }>,
"prototype-forged",
{ count: 10 },
),
),
];
const setMutationResults = [
mutationWasRejected(() => (publicSet as Set<string>).add("forged")),
mutationWasRejected(() =>
(publicSet as Set<string>).delete("registered"),
),
mutationWasRejected(() => (publicSet as Set<string>).clear()),
mutationWasRejected(() =>
Set.prototype.add.call(publicSet as Set<string>, "prototype-forged"),
),
];
const dateMutationResults = Object.getOwnPropertyNames(Date.prototype)
.filter((property) => property.startsWith("set"))
.map((property) =>
mutationWasRejected(() => {
const method = Reflect.get(publicDate, property);
if (typeof method !== "function") {
throw new TypeError(`date mutator unavailable: ${property}`);
}
Reflect.apply(method, publicDate, [0]);
}),
);
dateMutationResults.push(
mutationWasRejected(() => Date.prototype.setTime.call(publicDate, 0)),
);
let dateIsoAfter: string | undefined;
try {
dateIsoAfter = publicDate.toISOString();
} catch {
dateIsoAfter = undefined;
}
expect(dateMutationResults.length).toBeGreaterThan(1);
expect(dateMutationResults.every(Boolean)).toBe(true);
expect(escapeMutationResults).toEqual([true, true, true]);
expect(mapEntries).toEqual([["registered", 1]]);
expect(mapForEachReceivers).toHaveLength(1);
expect(mapForEachReceivers[0]).toBe(publicMap);
expect(mapMutationResults).toEqual([true, true, true, true]);
expect([...publicMap.entries()]).toEqual([["registered", { count: 1 }]]);
expect(setEntries).toEqual(["registered"]);
expect(setForEachReceivers).toHaveLength(1);
expect(setForEachReceivers[0]).toBe(publicSet);
expect(setMutationResults).toEqual([true, true, true, true]);
expect([...publicSet]).toEqual(["registered"]);
expect(dateIsoBefore).toBe("2026-08-26T12:34:56.000Z");
expect(dateIsoAfter).toBe("2026-08-26T12:34:56.000Z");
});
it("seals the global registry after deterministic bootstrap", () => {
sealHousekeepingCommandRegistry();
expect(() =>
registerHousekeepingCommand(command("people.registry.after-seal")),
).toThrow("command registry is sealed");
});
});
@@ -1,936 +0,0 @@
import { z } from "zod";
import { PERMS } from "@/lib/permission-slugs";
import {
HOUSEKEEPING_DOMAIN_IDS,
type HousekeepingDomainId,
} from "../../migration/types";
import type {
CapabilityRequirement,
HousekeepingCapabilityContext,
HousekeepingResult,
} from "../contracts";
export interface HousekeepingCommandContext {
readonly capability: HousekeepingCapabilityContext;
readonly correlationId: string;
readonly ipAddress: string;
}
export interface HousekeepingCommand<I, O> {
readonly id: string;
readonly owner: HousekeepingDomainId;
readonly risk: "safe" | "sensitive";
readonly capability: CapabilityRequirement;
/**
* Registration snapshots structural/built-in Zod graphs and resolvable lazy
* edges. Stateful custom refinements, transforms, preprocessors, and other
* executable schema callbacks are rejected. The registered public facade
* accepts callback-free parse, codec, and JSON-schema options only.
*/
readonly input: z.ZodType<I>;
readonly requiresReason: boolean;
readonly rateLimit: Readonly<{ attempts: number; windowMs: number }>;
readonly execute: (
context: HousekeepingCommandContext,
input: I,
) => Promise<HousekeepingResult<O>>;
}
export type UnsupportedHousekeepingCommandSchemaKind =
| "custom"
| "transform"
| "lazy"
| "executable";
export class UnsupportedHousekeepingCommandSchemaError extends Error {
readonly name = "UnsupportedHousekeepingCommandSchemaError";
readonly code = "UNSUPPORTED_COMMAND_INPUT_SCHEMA";
constructor(
readonly commandId: string,
readonly schemaKind: UnsupportedHousekeepingCommandSchemaKind,
) {
super(`unsupported command input schema: ${commandId} (${schemaKind})`);
}
}
type RegisteredHousekeepingCommand = HousekeepingCommand<unknown, unknown>;
type ZodInternalNode = {
readonly _zod: {
readonly constr: new (definition: never) => unknown;
readonly def: unknown;
};
};
const approvedOwners = new Set<string>(HOUSEKEEPING_DOMAIN_IDS);
const knownCapabilitySlugs = new Set<string>(Object.values(PERMS));
const commandIdPattern = /^[a-z][a-z0-9-]*(?:\.[a-z0-9][a-z0-9-]*)+$/;
const commands = new Map<string, RegisteredHousekeepingCommand>();
let sealed = false;
export function registerHousekeepingCommand<I, O>(
command: HousekeepingCommand<I, O>,
): void {
if (sealed) throw new Error("command registry is sealed");
validateCommand(command);
if (commands.has(command.id)) {
throw new Error(`duplicate command id: ${command.id}`);
}
const capability = Object.freeze({
mode: command.capability.mode,
slugs: Object.freeze([...command.capability.slugs]),
}) satisfies CapabilityRequirement;
const snapshot = Object.freeze({
id: command.id,
owner: command.owner,
risk: command.risk,
capability,
input: isolateValidationGraph(command.input, command.id),
requiresReason: command.requiresReason,
rateLimit: Object.freeze({ ...command.rateLimit }),
execute: command.execute,
}) as RegisteredHousekeepingCommand;
commands.set(snapshot.id, snapshot);
}
export function sealHousekeepingCommandRegistry(): void {
sealed = true;
}
export function getHousekeepingCommand(
id: string,
): RegisteredHousekeepingCommand | undefined {
return commands.get(id);
}
export function listHousekeepingCommands(): readonly HousekeepingCommand<
unknown,
unknown
>[] {
return Object.freeze([...commands.values()]);
}
export function isHousekeepingCommandId(value: unknown): value is string {
return isNormalizedIdentifier(value) && commandIdPattern.test(value);
}
function validateCommand<I, O>(command: HousekeepingCommand<I, O>): void {
if (!isHousekeepingCommandId(command.id)) {
throw new Error(`invalid command id: ${String(command.id)}`);
}
if (
!isNormalizedIdentifier(command.owner) ||
!approvedOwners.has(command.owner)
) {
throw new Error(`unknown command owner: ${String(command.owner)}`);
}
if (!command.id.startsWith(`${command.owner}.`)) {
throw new Error(`command owner mismatch: ${command.id}`);
}
if (command.risk !== "safe" && command.risk !== "sensitive") {
throw new Error(`invalid command risk: ${command.id}`);
}
if (typeof command.requiresReason !== "boolean") {
throw new Error(`invalid command reason policy: ${command.id}`);
}
validateCapability(command.capability, command.id);
if (!(command.input instanceof z.ZodType)) {
throw new Error(`command input schema missing: ${command.id}`);
}
if (typeof command.execute !== "function") {
throw new Error(`command execute missing: ${command.id}`);
}
if (
typeof command.rateLimit !== "object" ||
command.rateLimit === null ||
!Number.isInteger(command.rateLimit.attempts) ||
command.rateLimit.attempts <= 0 ||
!Number.isInteger(command.rateLimit.windowMs) ||
command.rateLimit.windowMs <= 0
) {
throw new Error(`invalid command rate limit: ${command.id}`);
}
}
function validateCapability(
requirement: CapabilityRequirement,
commandId: string,
): void {
if (
typeof requirement !== "object" ||
requirement === null ||
(requirement.mode !== "any" && requirement.mode !== "all") ||
!Array.isArray(requirement.slugs) ||
requirement.slugs.length === 0
) {
throw new Error(`invalid command capability: ${commandId}`);
}
for (const slug of requirement.slugs) {
if (!isNormalizedIdentifier(slug) || !knownCapabilitySlugs.has(slug)) {
throw new Error(`invalid command capability: ${commandId}`);
}
}
}
function isolateValidationGraph<T extends z.ZodType>(
schema: T,
commandId: string,
): T {
type SchemaCell = {
current?: z.ZodType;
reference?: z.ZodType;
};
const graphValues = new WeakMap<object, unknown>();
const schemaCells = new WeakMap<z.ZodType, SchemaCell>();
function unsupported(
schemaKind: UnsupportedHousekeepingCommandSchemaKind,
): never {
throw new UnsupportedHousekeepingCommandSchemaError(commandId, schemaKind);
}
function cloneSchema<S extends z.ZodType>(value: S): S {
const existing = schemaCells.get(value);
if (existing?.current) return existing.current as S;
if (existing) {
existing.reference ??= z.lazy(() => {
if (!existing.current) {
throw new Error("housekeeping schema registration incomplete");
}
return existing.current;
});
return existing.reference as S;
}
const definition = value.def as { type?: unknown; getter?: unknown };
if (definition.type === "custom") unsupported("custom");
if (definition.type === "transform") unsupported("transform");
const cell: SchemaCell = {};
schemaCells.set(value, cell);
if (definition.type === "lazy") {
let ownedTarget: z.ZodType | undefined;
const ownedLazy = z.lazy(() => {
if (!ownedTarget) {
throw new Error("housekeeping lazy schema target unavailable");
}
return ownedTarget;
});
cell.current = ownedLazy;
if (typeof definition.getter !== "function") unsupported("lazy");
let callerTarget: unknown;
try {
callerTarget = definition.getter();
} catch {
unsupported("lazy");
}
if (!(callerTarget instanceof z.ZodType)) unsupported("lazy");
ownedTarget = cloneSchema(callerTarget);
return ownedLazy as unknown as S;
}
const clonedDefinition = cloneGraph(value.def);
const cloned = value.clone(clonedDefinition as typeof value.def);
cell.current = cloned;
return cloned;
}
function cloneBuiltInCheck(value: ZodInternalNode): unknown {
const definition = value._zod.def;
if (typeof definition !== "object" || definition === null) {
unsupported("executable");
}
const checkDefinition = definition as {
check?: unknown;
type?: unknown;
};
if (
checkDefinition.check === "custom" ||
checkDefinition.type === "custom"
) {
unsupported("custom");
}
const clonedDefinition = cloneRecord(definition, true);
const cloned = new value._zod.constr(clonedDefinition as never) as {
_zod?: { check?: unknown };
};
if (
typeof (value._zod as { check?: unknown }).check === "function" &&
typeof cloned._zod?.check !== "function"
) {
unsupported("executable");
}
return cloned;
}
function cloneRecord(value: object, omitBuiltInWhen = false): object {
const cached = graphValues.get(value);
if (cached !== undefined) return cached as object;
const prototype = Object.getPrototypeOf(value);
const cloned = Object.create(prototype) as Record<PropertyKey, unknown>;
graphValues.set(value, cloned);
for (const key of Reflect.ownKeys(value)) {
const descriptor = Object.getOwnPropertyDescriptor(value, key);
if (!descriptor) continue;
if (
omitBuiltInWhen &&
key === "when" &&
"value" in descriptor &&
typeof descriptor.value === "function"
) {
continue;
}
let resolved: unknown;
if ("value" in descriptor) {
resolved = descriptor.value;
} else if (descriptor.get) {
try {
resolved = Reflect.get(value, key);
} catch {
unsupported("executable");
}
} else {
unsupported("executable");
}
Object.defineProperty(cloned, key, {
configurable: descriptor.configurable,
enumerable: descriptor.enumerable,
value: cloneGraph(resolved),
writable: "writable" in descriptor ? descriptor.writable : false,
});
}
return cloned;
}
function cloneGraph(value: unknown): unknown {
if (typeof value === "function") unsupported("executable");
if (typeof value !== "object" || value === null) return value;
if (value instanceof z.ZodType) return cloneSchema(value);
const cached = graphValues.get(value);
if (cached !== undefined) return cached;
if (isZodInternalNode(value)) return cloneBuiltInCheck(value);
if (Array.isArray(value)) {
const cloned: unknown[] = [];
graphValues.set(value, cloned);
for (const item of value) cloned.push(cloneGraph(item));
return cloned;
}
if (value instanceof RegExp) {
const cloned = new RegExp(value.source, value.flags);
graphValues.set(value, cloned);
return cloned;
}
if (value instanceof Date) {
const cloned = new Date(value.getTime());
graphValues.set(value, cloned);
return cloned;
}
if (value instanceof Map) {
const cloned = new Map<unknown, unknown>();
graphValues.set(value, cloned);
for (const [key, item] of value) {
cloned.set(cloneGraph(key), cloneGraph(item));
}
return cloned;
}
if (value instanceof Set) {
const cloned = new Set<unknown>();
graphValues.set(value, cloned);
for (const item of value) cloned.add(cloneGraph(item));
return cloned;
}
return cloneRecord(value);
}
return createReadonlyValidationFacade(cloneSchema(schema));
}
function isZodInternalNode(value: object): value is ZodInternalNode {
const internal = (value as { _zod?: unknown })._zod;
return (
typeof internal === "object" &&
internal !== null &&
typeof (internal as { constr?: unknown }).constr === "function" &&
"def" in internal
);
}
type ReadonlyIntrinsicMethod = (...args: never[]) => unknown;
const MAP_READ_METHODS = new Map<PropertyKey, ReadonlyIntrinsicMethod>([
["get", Map.prototype.get as unknown as ReadonlyIntrinsicMethod],
["has", Map.prototype.has as unknown as ReadonlyIntrinsicMethod],
["entries", Map.prototype.entries as unknown as ReadonlyIntrinsicMethod],
["keys", Map.prototype.keys as unknown as ReadonlyIntrinsicMethod],
["values", Map.prototype.values as unknown as ReadonlyIntrinsicMethod],
[
Symbol.iterator,
Map.prototype[Symbol.iterator] as unknown as ReadonlyIntrinsicMethod,
],
]);
const MAP_SIZE_GETTER = Object.getOwnPropertyDescriptor(Map.prototype, "size")
?.get as ReadonlyIntrinsicMethod | undefined;
const SET_READ_METHODS = new Map<PropertyKey, ReadonlyIntrinsicMethod>([
["has", Set.prototype.has as unknown as ReadonlyIntrinsicMethod],
["entries", Set.prototype.entries as unknown as ReadonlyIntrinsicMethod],
["keys", Set.prototype.keys as unknown as ReadonlyIntrinsicMethod],
["values", Set.prototype.values as unknown as ReadonlyIntrinsicMethod],
[
Symbol.iterator,
Set.prototype[Symbol.iterator] as unknown as ReadonlyIntrinsicMethod,
],
]);
const SET_SIZE_GETTER = Object.getOwnPropertyDescriptor(Set.prototype, "size")
?.get as ReadonlyIntrinsicMethod | undefined;
const DATE_READ_METHODS = new Map<PropertyKey, ReadonlyIntrinsicMethod>();
for (const property of Reflect.ownKeys(Date.prototype)) {
if (
property === "constructor" ||
(typeof property === "string" && property.startsWith("set"))
) {
continue;
}
const descriptor = Object.getOwnPropertyDescriptor(Date.prototype, property);
if (typeof descriptor?.value === "function") {
DATE_READ_METHODS.set(
property,
descriptor.value as ReadonlyIntrinsicMethod,
);
}
}
function createReadonlyValidationFacade<T extends z.ZodType>(schema: T): T {
const views = new WeakMap<object, unknown>();
const dataFirstValidationMethods = new Set<PropertyKey>([
"parse",
"safeParse",
"parseAsync",
"safeParseAsync",
"spa",
"encode",
"decode",
"encodeAsync",
"decodeAsync",
"safeEncode",
"safeDecode",
"safeEncodeAsync",
"safeDecodeAsync",
]);
const parseResultMethods = new Set<PropertyKey>([
...dataFirstValidationMethods,
"toJSONSchema",
]);
const unsafeCallbackMessage =
"callback-bearing schema arguments are not supported";
function rejectUnsafeCallback(): never {
throw new TypeError(unsafeCallbackMessage);
}
function inspectCallbackValues(
source: object,
seen: WeakSet<object>,
skipConstructor: boolean,
): void {
let keys: readonly PropertyKey[];
try {
keys = Reflect.ownKeys(source);
} catch {
rejectUnsafeCallback();
}
for (const key of keys) {
if (skipConstructor && key === "constructor") continue;
let descriptor: PropertyDescriptor | undefined;
try {
descriptor = Object.getOwnPropertyDescriptor(source, key);
} catch {
rejectUnsafeCallback();
}
if (!descriptor) continue;
if (!("value" in descriptor)) rejectUnsafeCallback();
assertCallbackFreeValue(descriptor.value, seen);
}
}
function isOpaqueValidationArgument(value: object): boolean {
try {
if (value instanceof z.ZodType) return true;
const internalDescriptor = Object.getOwnPropertyDescriptor(value, "_zod");
if (!internalDescriptor || !("value" in internalDescriptor)) {
return false;
}
const internal = internalDescriptor.value;
return (
typeof internal === "object" &&
internal !== null &&
typeof (internal as { constr?: unknown }).constr === "function" &&
"def" in internal
);
} catch {
rejectUnsafeCallback();
}
}
function assertCallbackFreeValue(
value: unknown,
seen: WeakSet<object>,
): void {
if (typeof value === "function") rejectUnsafeCallback();
if (typeof value !== "object" || value === null || seen.has(value)) {
return;
}
seen.add(value);
if (isOpaqueValidationArgument(value)) return;
inspectCallbackValues(value, seen, false);
if (
Array.isArray(value) ||
value instanceof Map ||
value instanceof Set ||
value instanceof WeakMap ||
value instanceof WeakSet ||
value instanceof Date ||
value instanceof RegExp ||
value instanceof Promise ||
ArrayBuffer.isView(value)
) {
return;
}
let prototype: object | null;
try {
prototype = Object.getPrototypeOf(value);
} catch {
rejectUnsafeCallback();
}
while (prototype && prototype !== Object.prototype) {
inspectCallbackValues(prototype, seen, true);
try {
prototype = Object.getPrototypeOf(prototype);
} catch {
rejectUnsafeCallback();
}
}
}
function assertCallbackFreeArguments(args: readonly unknown[]): void {
const seen = new WeakSet<object>();
for (const argument of args) assertCallbackFreeValue(argument, seen);
}
function readOption(options: object, property: PropertyKey): unknown {
try {
return Reflect.get(options, property, options);
} catch {
rejectUnsafeCallback();
}
}
function optionRecord(value: unknown): object {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
rejectUnsafeCallback();
}
return value;
}
function sanitizeParseOptions(value: unknown): object | undefined {
if (value === undefined) return undefined;
const options = optionRecord(value);
if (readOption(options, "error") !== undefined) rejectUnsafeCallback();
const safe = Object.create(null) as Record<string, unknown>;
for (const property of ["reportInput", "jitless"] as const) {
const option = readOption(options, property);
if (option === undefined) continue;
if (typeof option !== "boolean") rejectUnsafeCallback();
safe[property] = option;
}
return Object.freeze(safe);
}
function sanitizeJsonSchemaOptions(value: unknown): object | undefined {
if (value === undefined) return undefined;
const options = optionRecord(value);
for (const property of [
"override",
"processors",
"metadata",
"external",
] as const) {
if (readOption(options, property) !== undefined) {
rejectUnsafeCallback();
}
}
const safe = Object.create(null) as Record<string, unknown>;
for (const property of [
"target",
"unrepresentable",
"io",
"cycles",
"reused",
] as const) {
const option = readOption(options, property);
if (option === undefined) continue;
if (typeof option !== "string") rejectUnsafeCallback();
safe[property] = option;
}
return Object.freeze(safe);
}
function prepareSafeSchemaMethodArguments(
property: PropertyKey,
args: readonly unknown[],
): readonly unknown[] {
assertCallbackFreeArguments(
dataFirstValidationMethods.has(property) ? args.slice(1) : args,
);
if (dataFirstValidationMethods.has(property)) {
const options = sanitizeParseOptions(args[1]);
return options === undefined ? [args[0]] : [args[0], options];
}
if (property === "toJSONSchema") {
const options = sanitizeJsonSchemaOptions(args[0]);
return options === undefined ? [] : [options];
}
return args;
}
function readonlyView(value: unknown): unknown {
if (
(typeof value !== "object" && typeof value !== "function") ||
value === null
) {
return value;
}
const cached = views.get(value);
if (cached !== undefined) return cached;
if (value instanceof z.ZodType) return schemaFacade(value);
if (typeof value === "function") {
return functionFacade(value as (...args: never[]) => unknown);
}
if (Array.isArray(value)) return arrayFacade(value);
if (value instanceof Map) return mapFacade(value);
if (value instanceof Set) return setFacade(value);
if (value instanceof RegExp) {
const detached = new RegExp(value.source, value.flags);
views.set(value, detached);
return Object.freeze(detached);
}
if (value instanceof Date) return dateFacade(value);
return objectFacade(value);
}
function functionFacade(value: (...args: never[]) => unknown): unknown {
const wrapped = function (this: unknown, ...args: unknown[]) {
assertCallbackFreeArguments(args);
const result = new.target
? Reflect.construct(value, args)
: Reflect.apply(value, this, args);
return readonlyView(result);
};
views.set(value, wrapped);
return Object.freeze(wrapped);
}
function arrayFacade(value: readonly unknown[]): readonly unknown[] {
const detached: unknown[] = [];
views.set(value, detached);
for (const item of value) detached.push(readonlyView(item));
return Object.freeze(detached);
}
const rejectedMutation = Object.freeze((): never => {
throw new TypeError("readonly detached view");
});
function mapFacade(
value: ReadonlyMap<unknown, unknown>,
): ReadonlyMap<unknown, unknown> {
const detached = new Map<unknown, unknown>();
let facade: ReadonlyMap<unknown, unknown>;
facade = new Proxy(detached, {
defineProperty: () => false,
deleteProperty: () => false,
get: (target, property) => {
if (
property === "set" ||
property === "delete" ||
property === "clear"
) {
return rejectedMutation;
}
if (property === "forEach") {
return Object.freeze((callback: unknown, thisArg?: unknown): void => {
if (typeof callback !== "function") {
throw new TypeError("Map forEach callback missing");
}
for (const [key, item] of target) {
Reflect.apply(callback, thisArg, [item, key, facade]);
}
});
}
if (property === "size" && MAP_SIZE_GETTER) {
return Reflect.apply(MAP_SIZE_GETTER, target, []);
}
const intrinsic = MAP_READ_METHODS.get(property);
if (intrinsic) {
return Object.freeze((...args: unknown[]) =>
Reflect.apply(intrinsic, target, args),
);
}
return readonlyView(Reflect.get(target, property, facade));
},
set: () => false,
setPrototypeOf: () => false,
});
views.set(value, facade);
views.set(facade, facade);
for (const [key, item] of value) {
detached.set(readonlyView(key), readonlyView(item));
}
Object.freeze(detached);
return facade;
}
function setFacade(value: ReadonlySet<unknown>): ReadonlySet<unknown> {
const detached = new Set<unknown>();
let facade: ReadonlySet<unknown>;
facade = new Proxy(detached, {
defineProperty: () => false,
deleteProperty: () => false,
get: (target, property) => {
if (
property === "add" ||
property === "delete" ||
property === "clear"
) {
return rejectedMutation;
}
if (property === "forEach") {
return Object.freeze((callback: unknown, thisArg?: unknown): void => {
if (typeof callback !== "function") {
throw new TypeError("Set forEach callback missing");
}
for (const item of target) {
Reflect.apply(callback, thisArg, [item, item, facade]);
}
});
}
if (property === "size" && SET_SIZE_GETTER) {
return Reflect.apply(SET_SIZE_GETTER, target, []);
}
const intrinsic = SET_READ_METHODS.get(property);
if (intrinsic) {
return Object.freeze((...args: unknown[]) =>
Reflect.apply(intrinsic, target, args),
);
}
return readonlyView(Reflect.get(target, property, facade));
},
set: () => false,
setPrototypeOf: () => false,
});
views.set(value, facade);
views.set(facade, facade);
for (const item of value) detached.add(readonlyView(item));
Object.freeze(detached);
return facade;
}
function dateFacade(value: Date): Date {
const detached = new Date(value.getTime());
const facade = new Proxy(detached, {
defineProperty: () => false,
deleteProperty: () => false,
get: (target, property) => {
if (typeof property === "string" && property.startsWith("set")) {
return rejectedMutation;
}
const intrinsic = DATE_READ_METHODS.get(property);
if (intrinsic) {
return Object.freeze((...args: unknown[]) =>
Reflect.apply(intrinsic, target, args),
);
}
return readonlyView(Reflect.get(target, property, facade));
},
set: () => false,
setPrototypeOf: () => false,
});
views.set(value, facade);
views.set(facade, facade);
Object.freeze(detached);
return facade;
}
function objectFacade(value: object): object {
const detached = Object.create(Object.getPrototypeOf(value)) as Record<
PropertyKey,
unknown
>;
views.set(value, detached);
copyReadonlyProperties(value, detached, (method) =>
boundFunctionFacade(method, value),
);
return Object.freeze(detached);
}
function boundFunctionFacade(
method: (...args: never[]) => unknown,
receiver: object,
): (...args: unknown[]) => unknown {
return Object.freeze((...args: unknown[]) => {
assertCallbackFreeArguments(args);
return readonlyView(Reflect.apply(method, receiver, args));
});
}
function copyReadonlyProperties(
source: object,
destination: Record<PropertyKey, unknown>,
wrapMethod: (
method: (...args: never[]) => unknown,
property: PropertyKey,
) => unknown,
): void {
for (const key of Reflect.ownKeys(source)) {
const descriptor = Object.getOwnPropertyDescriptor(source, key);
if (!descriptor) continue;
if ("value" in descriptor) {
Object.defineProperty(destination, key, {
configurable: false,
enumerable: descriptor.enumerable,
value:
typeof descriptor.value === "function"
? wrapMethod(descriptor.value, key)
: readonlyView(descriptor.value),
writable: false,
});
continue;
}
const getter = descriptor.get;
const safeGetter = getter
? () => readonlyView(Reflect.apply(getter, source, []))
: undefined;
Object.defineProperty(destination, key, {
configurable: false,
enumerable: descriptor.enumerable,
get: safeGetter,
});
}
}
function materializeSchemaPrototypeMethods(target: z.ZodType): void {
let prototype = Object.getPrototypeOf(target);
while (prototype && prototype !== Object.prototype) {
for (const property of Reflect.ownKeys(prototype)) {
if (Object.hasOwn(target, property)) continue;
const descriptor = Object.getOwnPropertyDescriptor(prototype, property);
if (
!descriptor?.get ||
!descriptor.set ||
!descriptor.configurable ||
descriptor.enumerable
) {
continue;
}
Reflect.get(target, property, target);
}
prototype = Object.getPrototypeOf(prototype);
}
}
function schemaFacade<S extends z.ZodType>(target: S): S {
const cached = views.get(target);
if (cached !== undefined) return cached as S;
materializeSchemaPrototypeMethods(target);
const shell = Object.create(Object.getPrototypeOf(target)) as Record<
PropertyKey,
unknown
>;
let facade: S;
facade = new Proxy(shell, {
defineProperty: () => false,
deleteProperty: () => false,
get: (publicShell, property, receiver) => {
if (Object.hasOwn(publicShell, property)) {
return Reflect.get(publicShell, property, receiver);
}
const inherited = Reflect.get(publicShell, property, receiver);
return readonlyView(inherited);
},
set: () => false,
setPrototypeOf: () => false,
}) as unknown as S;
views.set(target, facade);
copyReadonlyProperties(target, shell, (method, property) =>
schemaMethodFacade(method, property, target, facade),
);
Object.freeze(shell);
return facade;
}
function schemaMethodFacade<S extends z.ZodType>(
method: (...args: never[]) => unknown,
property: PropertyKey,
target: S,
facade: S,
): (...args: unknown[]) => unknown {
if (property === "apply") {
return Object.freeze((...args: unknown[]) => {
const callback = args[0];
if (typeof callback !== "function") {
throw new TypeError("schema apply callback missing");
}
return Reflect.apply(callback, undefined, [facade]);
});
}
if (property === "register") {
return Object.freeze((...args: unknown[]) => {
const registry = args[0];
if (
typeof registry !== "object" ||
registry === null ||
typeof (registry as { add?: unknown }).add !== "function"
) {
throw new TypeError("schema registry missing");
}
(
registry as {
add(schema: S, metadata?: unknown): unknown;
}
).add(facade, args[1]);
return facade;
});
}
return Object.freeze((...args: unknown[]) => {
const safeArgs = prepareSafeSchemaMethodArguments(property, args);
const result = Reflect.apply(method, target, safeArgs);
if (result instanceof z.ZodType) {
return isolateValidationGraph(result, "derived-schema");
}
return parseResultMethods.has(property) ? result : readonlyView(result);
});
}
return schemaFacade(schema);
}
function isNormalizedIdentifier(value: unknown): value is string {
return (
typeof value === "string" &&
value.length > 0 &&
value.length <= 160 &&
value === value.normalize("NFC").trim()
);
}
@@ -13,7 +13,6 @@ import {
type HousekeepingSearchResult,
type HousekeepingWidgetDefinition,
type HousekeepingWorkItem,
mapUnknownError,
ok,
} from ".";
@@ -26,32 +25,18 @@ const workItem = {
domain: "people",
capability,
severity: "warning",
priority: "high",
ageMs: 60_000,
state: "open",
occurredAt: "2026-08-24T12:00:00.000Z",
titleKey: "pages.housekeeping.items.ticket",
context: { ticketId: "42" },
href: "/ase/people/tickets/42",
href: "/admin-next/people/tickets/42",
freshness: "fresh",
actions: [
{
id: "people.ticket.open",
labelKey: "pages.housekeeping.items.ticket.open",
commandId: "people.ticket.open",
capability,
},
],
} satisfies HousekeepingWorkItem;
const searchResult = {
id: "user-42",
domain: "people",
type: "entity",
title: "operator",
description: "A Housekeeping operator",
href: "/ase/people/users/42",
capability,
href: "/admin-next/people/users/42",
} satisfies HousekeepingSearchResult;
const searchProvider: HousekeepingSearchProvider = {
@@ -102,7 +87,7 @@ const manifest: HousekeepingDomainManifest = {
labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description",
iconId: "users",
canonicalHref: "/ase/people",
previewHref: "/admin-next/people",
capability,
routes: [],
searchProviders: [searchProvider],
@@ -111,68 +96,19 @@ const manifest: HousekeepingDomainManifest = {
};
describe("housekeeping foundation contracts", () => {
it("preserves correlation IDs for successful results", () => {
it("creates typed success and error results", () => {
expect(ok({ count: 2 }, "corr-1")).toEqual({
ok: true,
data: { count: 2 },
correlationId: "corr-1",
});
});
it.each([
"UNAUTHENTICATED",
"FORBIDDEN",
"VALIDATION",
"NOT_FOUND",
"CONFLICT",
"RATE_LIMITED",
"DEPENDENCY_UNAVAILABLE",
"TIMEOUT",
"INTERNAL",
] as const)("returns the approved %s error contract", (code) => {
expect(
fail(code, `errors.housekeeping.${code.toLowerCase()}`, "corr-2", {
name: ["errors.validation.required"],
}),
).toEqual({
expect(fail("CAPABILITY_DENIED", "corr-2")).toEqual({
ok: false,
error: {
code,
messageKey: `errors.housekeeping.${code.toLowerCase()}`,
fieldErrors: { name: ["errors.validation.required"] },
},
error: { code: "CAPABILITY_DENIED" },
correlationId: "corr-2",
});
});
it("preserves a supplied correlation ID when mapping an unknown error", () => {
expect(
mapUnknownError(new Error("database password exposed"), "corr-unknown"),
).toEqual({
ok: false,
error: {
code: "INTERNAL",
messageKey: "errors.housekeeping.internal",
},
correlationId: "corr-unknown",
});
});
it("generates a correlation ID when mapping an unknown error without one", () => {
const result = mapUnknownError(new Error("database password exposed"));
expect(result).toMatchObject({
ok: false,
error: {
code: "INTERNAL",
messageKey: "errors.housekeeping.internal",
},
});
expect(result.correlationId).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
);
});
it("rejects empty capability requirements", () => {
expect(() => anyCapability()).toThrow("capability requirement is empty");
expect(() => allCapabilities()).toThrow("capability requirement is empty");
@@ -4,11 +4,10 @@ import type { HousekeepingInboxSource } from "./inbox";
import type { HousekeepingSearchProvider } from "./search";
import type { HousekeepingWidgetDefinition } from "./widget";
export type CanonicalHousekeepingHref = "/ase" | `/ase/${string}`;
export interface HousekeepingRouteDefinition {
id: string;
labelKey: string;
href: CanonicalHousekeepingHref;
href: string;
capability: CapabilityRequirement;
matchPrefixes?: readonly string[];
}
@@ -18,7 +17,7 @@ export interface HousekeepingDomainManifest {
labelKey: string;
descriptionKey: string;
iconId: "inbox" | "users" | "file-text" | "gem" | "hotel" | "settings";
canonicalHref: CanonicalHousekeepingHref;
previewHref: `/admin-next/${HousekeepingDomainId}`;
capability: CapabilityRequirement;
routes: readonly HousekeepingRouteDefinition[];
searchProviders: readonly HousekeepingSearchProvider[];
@@ -3,25 +3,12 @@ import type {
CapabilityRequirement,
HousekeepingCapabilityContext,
} from "./capability";
import type { CanonicalHousekeepingHref } from "./domain";
import type { HousekeepingResult } from "./result";
export type HousekeepingWorkItemSeverity = "info" | "warning" | "critical";
export type HousekeepingWorkItemFreshness = "fresh" | "stale";
export type HousekeepingWorkItemPriority =
| "low"
| "normal"
| "high"
| "critical";
export type HousekeepingInboxSourceAvailability = "available" | "unavailable";
export interface HousekeepingWorkItemAction {
id: string;
labelKey: string;
commandId: string;
capability: CapabilityRequirement;
}
export interface HousekeepingWorkItem {
sourceId: string;
itemId: string;
@@ -29,15 +16,11 @@ export interface HousekeepingWorkItem {
domain: HousekeepingDomainId;
capability: CapabilityRequirement;
severity: HousekeepingWorkItemSeverity;
priority: HousekeepingWorkItemPriority;
ageMs: number;
state: string;
occurredAt: string;
titleKey: string;
context?: Readonly<Record<string, unknown>>;
href: CanonicalHousekeepingHref;
href: string;
freshness: HousekeepingWorkItemFreshness;
actions: readonly HousekeepingWorkItemAction[];
}
export interface HousekeepingInboxSourceResult {
@@ -1,4 +1,3 @@
export { createCorrelationId } from "../correlation";
export {
allCapabilities,
anyCapability,
@@ -8,7 +7,6 @@ export {
} from "./capability";
export type { HousekeepingCommand } from "./command";
export type {
CanonicalHousekeepingHref,
HousekeepingDomainManifest,
HousekeepingRouteDefinition,
} from "./domain";
@@ -17,26 +15,20 @@ export type {
HousekeepingInboxSourceAvailability,
HousekeepingInboxSourceResult,
HousekeepingWorkItem,
HousekeepingWorkItemAction,
HousekeepingWorkItemFreshness,
HousekeepingWorkItemPriority,
HousekeepingWorkItemSeverity,
} from "./inbox";
export type { HousekeepingQuery } from "./query";
export {
fail,
type HousekeepingError,
type HousekeepingErrorCode,
type HousekeepingPartialCompletion,
type HousekeepingResult,
mapUnknownError,
ok,
} from "./result";
export type {
HousekeepingSearchInput,
HousekeepingSearchProvider,
HousekeepingSearchResult,
HousekeepingSearchResultType,
} from "./search";
export type {
HousekeepingWidgetDefinition,
@@ -1,70 +1,28 @@
import { createCorrelationId } from "../correlation";
export type HousekeepingErrorCode =
| "UNAUTHENTICATED"
| "FORBIDDEN"
| "VALIDATION"
| "VALIDATION_FAILED"
| "AUTHENTICATION_REQUIRED"
| "CAPABILITY_DENIED"
| "NOT_FOUND"
| "CONFLICT"
| "RATE_LIMITED"
| "DEPENDENCY_UNAVAILABLE"
| "TIMEOUT"
| "INTERNAL";
export interface HousekeepingError {
code: HousekeepingErrorCode;
messageKey: string;
fieldErrors?: Readonly<Record<string, readonly string[]>>;
}
export interface HousekeepingPartialCompletion {
readonly status: "partial";
readonly external: "not-required" | "completed" | "failed";
readonly audit: "persisted" | "unavailable";
}
| "PARTIAL_RESULT"
| "INTERNAL_ERROR";
export type HousekeepingResult<T> =
| { ok: true; data: T; correlationId: string }
| {
ok: true;
data: T;
ok: false;
error: { code: HousekeepingErrorCode };
correlationId: string;
completion?: HousekeepingPartialCompletion;
}
| { ok: false; error: HousekeepingError; correlationId: string };
};
export function ok<T>(
data: T,
correlationId: string,
completion?: HousekeepingPartialCompletion,
): HousekeepingResult<T> {
return {
ok: true,
data,
correlationId,
...(completion === undefined ? {} : { completion }),
};
export function ok<T>(data: T, correlationId: string): HousekeepingResult<T> {
return { ok: true, data, correlationId };
}
export function fail(
code: HousekeepingErrorCode,
messageKey: string,
correlationId: string,
fieldErrors?: Readonly<Record<string, readonly string[]>>,
): HousekeepingResult<never> {
return {
ok: false,
error: {
code,
messageKey,
...(fieldErrors === undefined ? {} : { fieldErrors }),
},
correlationId,
};
}
export function mapUnknownError(
_error: unknown,
correlationId = createCorrelationId(),
): HousekeepingResult<never> {
return fail("INTERNAL", "errors.housekeeping.internal", correlationId);
return { ok: false, error: { code }, correlationId };
}
@@ -3,7 +3,6 @@ import type {
CapabilityRequirement,
HousekeepingCapabilityContext,
} from "./capability";
import type { CanonicalHousekeepingHref } from "./domain";
import type { HousekeepingResult } from "./result";
export interface HousekeepingSearchInput {
@@ -11,16 +10,11 @@ export interface HousekeepingSearchInput {
limit: number;
}
export type HousekeepingSearchResultType = "navigation" | "entity" | "command";
export interface HousekeepingSearchResult {
id: string;
domain: HousekeepingDomainId;
type: HousekeepingSearchResultType;
title: string;
description?: string;
href: CanonicalHousekeepingHref;
capability: CapabilityRequirement;
href: string;
}
export interface HousekeepingSearchProvider {
@@ -14,6 +14,5 @@ export interface HousekeepingWidgetDefinition {
kind: HousekeepingWidgetKind;
load(
context: HousekeepingCapabilityContext,
signal: AbortSignal,
): Promise<HousekeepingResult<unknown>>;
}
@@ -1,17 +0,0 @@
import { describe, expect, it } from "vitest";
import { createCorrelationId } from "./correlation";
describe("createCorrelationId", () => {
it("creates UUID-backed identifiers within the audit storage limit", () => {
const correlationId = createCorrelationId();
expect(correlationId).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,
);
expect(correlationId.length).toBeLessThanOrEqual(64);
});
it("does not reuse an identifier for separate operations", () => {
expect(createCorrelationId()).not.toBe(createCorrelationId());
});
});
@@ -1,3 +0,0 @@
export function createCorrelationId(): string {
return crypto.randomUUID();
}
File diff suppressed because it is too large. Load diff
@@ -1,69 +0,0 @@
const HOUSEKEEPING_ORIGIN = "https://housekeeping.invalid";
function containsControlCharacter(value: string): boolean {
return Array.from(value).some((character) => {
const code = character.codePointAt(0) ?? 0;
return code < 32 || code === 127;
});
}
function decodedVariants(value: string): readonly string[] | null {
const variants = [value];
for (let pass = 0; pass < 3; pass += 1) {
let decoded: string;
try {
decoded = decodeURIComponent(variants.at(-1) ?? "");
} catch {
return null;
}
if (decoded === variants.at(-1)) break;
variants.push(decoded);
}
return variants;
}
function containsDotSegment(path: string): boolean {
return path.split("/").some((segment) => segment === "." || segment === "..");
}
export function isSafeHousekeepingHref(href: string): boolean {
if (
href.length === 0 ||
!href.startsWith("/") ||
href.startsWith("//") ||
containsControlCharacter(href) ||
href.includes("\\")
) {
return false;
}
const variants = decodedVariants(href);
if (
variants === null ||
variants.some(
(value) => containsControlCharacter(value) || value.includes("\\"),
)
) {
return false;
}
const rawPath = href.split(/[?#]/u, 1)[0] ?? "";
const pathVariants = decodedVariants(rawPath);
if (
pathVariants === null ||
pathVariants.some((path) => containsDotSegment(path))
) {
return false;
}
let normalized: URL;
try {
normalized = new URL(href, HOUSEKEEPING_ORIGIN);
} catch {
return false;
}
return (
normalized.origin === HOUSEKEEPING_ORIGIN &&
(normalized.pathname === "/ase" || normalized.pathname.startsWith("/ase/"))
);
}
@@ -1,221 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import {
anyCapability,
fail,
type HousekeepingCapabilityContext,
type HousekeepingDomainManifest,
type HousekeepingInboxSource,
type HousekeepingWorkItem,
ok,
} from "../contracts";
import type { HousekeepingRegistry } from "../registry";
import { createHousekeepingInboxService, INBOX_POLICY } from "./inbox-service";
function context(granted: readonly string[]): HousekeepingCapabilityContext {
const permissions = new Set(granted);
return {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => permissions.has(slug),
hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)),
hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)),
};
}
function item(
itemId: string,
options: Partial<HousekeepingWorkItem> = {},
): HousekeepingWorkItem {
return {
sourceId: "people.queue",
itemId,
deduplicationKey: `people.queue:${itemId}`,
domain: "people",
capability: anyCapability(PERMS.USERS_VIEW),
severity: "info",
priority: "normal",
ageMs: 1_000,
state: "open",
occurredAt: "2026-08-30T10:00:00.000Z",
titleKey: "pages.housekeeping.items.test",
href: "/ase/people/users",
freshness: "fresh",
actions: [],
...options,
};
}
function source(
id: string,
permission: string,
getItems: HousekeepingInboxSource["getItems"],
owner: "people" | "content" = "people",
): HousekeepingInboxSource {
return {
id,
owner,
capability: anyCapability(permission),
getItems,
};
}
function manifest(
id: "people" | "content",
permission: string,
inboxSources: readonly HousekeepingInboxSource[],
): HousekeepingDomainManifest {
return {
id,
labelKey: `pages.housekeeping.domains.${id}.title`,
descriptionKey: `pages.housekeeping.domains.${id}.description`,
iconId: id === "people" ? "users" : "file-text",
canonicalHref: `/ase/${id}`,
capability: anyCapability(permission),
routes: [],
searchProviders: [],
inboxSources,
widgets: [],
};
}
function registry(
...domains: readonly HousekeepingDomainManifest[]
): HousekeepingRegistry {
return { domains };
}
describe("Housekeeping inbox service", () => {
it("locks timeout, cap, and deduplication policy", () => {
expect(INBOX_POLICY).toEqual({
timeoutMs: 2_000,
combinedLimit: 200,
dedupe: "sourceId:itemId",
});
});
it("filters domain and source capabilities before invoking sources", async () => {
const permitted = vi.fn(async () =>
ok({ availability: "available" as const, items: [] }, "permitted"),
);
const forbiddenSource = vi.fn(async () =>
ok({ availability: "available" as const, items: [] }, "source"),
);
const forbiddenDomain = vi.fn(async () =>
ok({ availability: "available" as const, items: [] }, "domain"),
);
const service = createHousekeepingInboxService({
registry: registry(
manifest("people", PERMS.USERS_VIEW, [
source("people.ready", PERMS.USERS_VIEW, permitted),
source("people.bans", PERMS.BANS_VIEW, forbiddenSource),
]),
manifest("content", PERMS.NEWS_VIEW, [
source("content.users", PERMS.USERS_VIEW, forbiddenDomain, "content"),
]),
),
createCorrelationId: () => "inbox-capabilities",
});
await service(context([PERMS.USERS_VIEW]));
expect(permitted).toHaveBeenCalledOnce();
expect(forbiddenSource).not.toHaveBeenCalled();
expect(forbiddenDomain).not.toHaveBeenCalled();
});
it("deduplicates by source/item and orders by priority then oldest age", async () => {
const items = [
item("normal", { priority: "normal", ageMs: 900 }),
item("high-new", { priority: "high", ageMs: 100 }),
item("critical", { priority: "critical", ageMs: 10 }),
item("high-old", { priority: "high", ageMs: 1_000 }),
item("low", { priority: "low", ageMs: 5_000 }),
item("high-old", { priority: "high", ageMs: 999 }),
];
const service = createHousekeepingInboxService({
registry: registry(
manifest("people", PERMS.USERS_VIEW, [
source("people.queue", PERMS.USERS_VIEW, async () =>
ok({ availability: "available", items }, "ordered"),
),
]),
),
createCorrelationId: () => "inbox-order",
});
const response = await service(context([PERMS.USERS_VIEW]));
expect(response.items.map((entry) => entry.itemId)).toEqual([
"critical",
"high-old",
"high-new",
"normal",
"low",
]);
});
it("filters item capabilities before enforcing the 200-item cap", async () => {
const items = Array.from({ length: 230 }, (_, index) =>
item(String(index), {
capability: anyCapability(
index < 20 ? PERMS.BANS_VIEW : PERMS.USERS_VIEW,
),
}),
);
const service = createHousekeepingInboxService({
registry: registry(
manifest("people", PERMS.USERS_VIEW, [
source("people.queue", PERMS.USERS_VIEW, async () =>
ok({ availability: "available", items }, "capped"),
),
]),
),
createCorrelationId: () => "inbox-cap",
});
const response = await service(context([PERMS.USERS_VIEW]));
expect(response.items).toHaveLength(200);
expect(response.items[0]?.itemId).toBe("20");
expect(response.items.at(-1)?.itemId).toBe("219");
});
it("returns successful items with stable timeout and source errors", async () => {
let timeoutSignal: AbortSignal | undefined;
const service = createHousekeepingInboxService({
registry: registry(
manifest("people", PERMS.USERS_VIEW, [
source("people.timeout", PERMS.USERS_VIEW, (_context, signal) => {
timeoutSignal = signal;
return new Promise(() => undefined);
}),
source("people.failed", PERMS.USERS_VIEW, async () =>
fail(
"DEPENDENCY_UNAVAILABLE",
"errors.housekeeping.dependencyUnavailable",
"failed",
),
),
source("people.ready", PERMS.USERS_VIEW, async () =>
ok(
{
availability: "available",
items: [item("ready", { sourceId: "people.ready" })],
},
"ready",
),
),
]),
),
createCorrelationId: () => "inbox-partial",
timeoutMs: 5,
});
const response = await service(context([PERMS.USERS_VIEW]));
expect(response.items.map((entry) => entry.itemId)).toEqual(["ready"]);
expect(response.errors).toEqual([
{ sourceId: "people.timeout", code: "TIMEOUT" },
{ sourceId: "people.failed", code: "DEPENDENCY_UNAVAILABLE" },
]);
expect(timeoutSignal?.aborted).toBe(true);
expect(response.correlationId).toBe("inbox-partial");
});
});
@@ -1,206 +0,0 @@
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<HousekeepingWorkItem> {
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<HousekeepingInboxResponse> {
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<string>();
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<HousekeepingInboxResponse> {
const { HOUSEKEEPING_MANIFESTS } = await import("../../manifests");
const service = createHousekeepingInboxService({
registry: createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS),
});
return service(context);
}
@@ -1,256 +1,116 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
import ar from "@/messages/ar.json";
import bg from "@/messages/bg.json";
import cs from "@/messages/cs.json";
import da from "@/messages/da.json";
import de from "@/messages/de.json";
import el from "@/messages/el.json";
import en from "@/messages/en.json";
import es from "@/messages/es.json";
import fi from "@/messages/fi.json";
import fr from "@/messages/fr.json";
import hr from "@/messages/hr.json";
import hu from "@/messages/hu.json";
import itMessages from "@/messages/it.json";
import ja from "@/messages/ja.json";
import nl from "@/messages/nl.json";
import no from "@/messages/no.json";
import pl from "@/messages/pl.json";
import pt from "@/messages/pt.json";
import ro from "@/messages/ro.json";
import ru from "@/messages/ru.json";
import sk from "@/messages/sk.json";
import sr from "@/messages/sr.json";
import sv from "@/messages/sv.json";
import tr from "@/messages/tr.json";
import uk from "@/messages/uk.json";
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
const locales = {
ar,
bg,
cs,
da,
de,
el,
en,
es,
fi,
fr,
hr,
hu,
it: itMessages,
ja,
nl,
no,
pl,
pt,
ro,
ru,
sk,
sr,
sv,
tr,
uk,
} as const;
const commandFiles = [
"domains/content/commands/content-commands.ts",
"domains/economy/commands/economy-commands.ts",
"domains/hotel/commands/hotel-commands.ts",
"domains/hotel/commands/studio-commands.ts",
"domains/people/commands/user-commands.ts",
"domains/people/commands/community-commands.ts",
"domains/people/commands/support-commands.ts",
"domains/people/commands/moderation-commands.ts",
"domains/system/commands/system-commands.ts",
] as const;
const commandIds = [
...new Set(
commandFiles.flatMap((path) => {
const source = readFileSync(
resolve(process.cwd(), "src/features/housekeeping", path),
"utf8",
);
return [
...source.matchAll(
/"((?:content|economy|hotel|people|system)(?:\.[a-z0-9-]+)+)"/gu,
),
]
.map((match) => match[1])
.filter((id): id is string => id !== undefined);
}),
),
].sort();
const staticKeys = [
"preview.badge",
"preview.commandDisabled",
"preview.backToSite",
"navigation.skipToContent",
"navigation.primary",
"navigation.contextual",
"navigation.operatorRank",
"commandDeck.placeholder",
"commandDeck.navigation",
"commandDeck.commands",
"commandDeck.entities",
"commandDeck.loading",
"commandDeck.empty",
"commandDeck.partial",
"commandDeck.close",
"workspace.title",
"workspace.description",
"workspace.noDomainsTitle",
"workspace.noDomainsDescription",
"workspace.partial",
"workspace.inbox.title",
"workspace.inbox.domainFilter",
"workspace.inbox.stateFilter",
"workspace.inbox.priorityFilter",
"workspace.inbox.all",
"workspace.inbox.loading",
"workspace.inbox.empty",
"workspace.inbox.partial",
"workspace.inbox.open",
"workspace.recent.title",
"workspace.recent.empty",
"workspace.favorites.title",
"workspace.favorites.pin",
"workspace.favorites.unpin",
"workspace.favorites.error",
"workspace.favorites.reorderInstructions",
"workspace.widgetSettings.title",
"workspace.widgetSettings.mandatory",
"workspace.widgetSettings.error",
"workspace.widgets.title",
"workspace.widgets.partial",
"workspace.widgets.error",
"workspace.widgets.reorderInstructions",
] as const;
const stateKeys = [
"loading",
"empty",
"partial",
"error",
"forbidden",
"ready",
].flatMap((state) => [`states.${state}.title`, `states.${state}.description`]);
const manifestKeys = HOUSEKEEPING_MANIFESTS.flatMap((manifest) => [
manifest.labelKey.slice("pages.housekeeping.".length),
manifest.descriptionKey.slice("pages.housekeeping.".length),
...manifest.routes.map((route) =>
route.labelKey.slice("pages.housekeeping.".length),
),
...manifest.widgets.map((widget) => `widgets.${widget.id}`),
]);
const requiredKeys = [
...staticKeys,
...stateKeys,
"states.partial.label",
...manifestKeys,
...commandIds.map((id) => `commands.${id}`),
"pages.housekeeping.preview.badge",
"pages.housekeeping.preview.commandDisabled",
"pages.housekeeping.preview.backToSite",
"pages.housekeeping.navigation.skipToContent",
"pages.housekeeping.navigation.primary",
"pages.housekeeping.navigation.contextual",
"pages.housekeeping.states.loading.title",
"pages.housekeeping.states.loading.description",
"pages.housekeeping.states.empty.title",
"pages.housekeeping.states.empty.description",
"pages.housekeeping.states.partial.label",
"pages.housekeeping.states.partial.title",
"pages.housekeeping.states.partial.description",
"pages.housekeeping.states.error.title",
"pages.housekeeping.states.error.description",
];
const expectedDomainMessages = [
{
id: "operations",
labelKey: "pages.housekeeping.domains.operations.title",
descriptionKey: "pages.housekeeping.domains.operations.description",
},
{
id: "people",
labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description",
},
{
id: "content",
labelKey: "pages.housekeeping.domains.content.title",
descriptionKey: "pages.housekeeping.domains.content.description",
},
{
id: "economy",
labelKey: "pages.housekeeping.domains.economy.title",
descriptionKey: "pages.housekeeping.domains.economy.description",
},
{
id: "hotel",
labelKey: "pages.housekeeping.domains.hotel.title",
descriptionKey: "pages.housekeeping.domains.hotel.description",
},
{
id: "system",
labelKey: "pages.housekeeping.domains.system.title",
descriptionKey: "pages.housekeeping.domains.system.description",
},
] as const;
function resolveMessage(messages: unknown, key: string): unknown {
return key.split(".").reduce<unknown>((value, segment) => {
if (value === null || typeof value !== "object") return undefined;
return (value as Record<string, unknown>)[segment];
}, messages);
}
describe("housekeeping localization contract", () => {
it("covers exactly the 25 supported locale files", () => {
expect(Object.keys(locales)).toEqual([
"ar",
"bg",
"cs",
"da",
"de",
"el",
"en",
"es",
"fi",
"fr",
"hr",
"hu",
"it",
"ja",
"nl",
"no",
"pl",
"pt",
"ro",
"ru",
"sk",
"sr",
"sv",
"tr",
"uk",
it.each([
["English", en],
["Italian", itMessages],
])("provides the planned housekeeping subtree in %s", (_locale, messages) => {
const housekeeping = resolveMessage(messages, "pages.housekeeping");
expect(housekeeping).toEqual(expect.any(Object));
expect(Object.keys(housekeeping as object)).toEqual([
"preview",
"navigation",
"domains",
"states",
]);
expect(commandIds.length).toBeGreaterThan(100);
expect(commandIds.every((id) => !id.endsWith("."))).toBe(true);
});
it.each(Object.entries(locales))(
"provides every manifest, route, command, widget, and state key in %s",
(locale, messages) => {
for (const key of requiredKeys) {
const fullKey = `pages.housekeeping.${key}`;
const value = resolveMessage(messages, fullKey);
expect(value, `${locale}: ${fullKey}`).toEqual(expect.any(String));
expect((value as string).trim(), `${locale}: ${fullKey}`).not.toBe("");
expect(value, `${locale}: ${fullKey}`).not.toBe(fullKey);
}
},
);
for (const key of requiredKeys) {
expect(resolveMessage(messages, key), key).toEqual(expect.any(String));
}
it("does not use English shell copy as the fallback for another locale", () => {
const operatorKeys = [
"preview.backToSite",
"commandDeck.navigation",
"commandDeck.commands",
"commandDeck.entities",
"commandDeck.close",
"workspace.favorites.pin",
"workspace.favorites.unpin",
"workspace.widgetSettings.mandatory",
] as const;
for (const [locale, messages] of Object.entries(locales)) {
if (locale === "en") continue;
expect(messages.pages.housekeeping.preview.badge).not.toBe(
en.pages.housekeeping.preview.badge,
);
expect(messages.pages.housekeeping.states.loading.description).not.toBe(
en.pages.housekeeping.states.loading.description,
);
for (const key of operatorKeys) {
expect(
resolveMessage(messages.pages.housekeeping, key),
`${locale}: pages.housekeeping.${key}`,
).not.toBe(resolveMessage(en.pages.housekeeping, key));
}
for (const [index, expected] of expectedDomainMessages.entries()) {
const manifest = HOUSEKEEPING_MANIFESTS[index];
expect(manifest).toMatchObject(expected);
expect(
resolveMessage(messages, expected.labelKey),
expected.labelKey,
).toEqual(expect.any(String));
expect(
resolveMessage(messages, expected.descriptionKey),
expected.descriptionKey,
).toEqual(expect.any(String));
}
});
it("keeps idiomatic Italian shell copy", () => {
expect(itMessages.pages.housekeeping.preview.badge).toBe(
it("uses idiomatic Italian copy for the housekeeping shell", () => {
expect(resolveMessage(itMessages, "pages.housekeeping.preview.badge")).toBe(
"Anteprima della struttura",
);
expect(itMessages.pages.housekeeping.navigation.primary).toBe(
"Aree housekeeping",
);
expect(itMessages.pages.housekeeping.commandDeck.placeholder).toBe(
"Cerca navigazione, comandi ed entità",
);
expect(itMessages.pages.housekeeping.states.loading.description).toBe(
"Stiamo preparando gli strumenti di housekeeping disponibili.",
);
expect(
resolveMessage(itMessages, "pages.housekeeping.navigation.primary"),
).toBe("Aree housekeeping");
expect(
resolveMessage(itMessages, "pages.housekeeping.states.loading.title"),
).toBe("Caricamento in corso");
expect(
resolveMessage(
itMessages,
"pages.housekeeping.states.loading.description",
),
).toBe("Stiamo preparando gli strumenti di housekeeping disponibili.");
});
});
@@ -23,7 +23,7 @@ describe("housekeeping navigation", () => {
labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description",
iconId: "users",
canonicalHref: "/ase/people",
previewHref: "/admin-next/people",
capability: anyCapability(PERMS.MOD_CFH_VIEW),
routes: [],
searchProviders: [],
@@ -35,7 +35,7 @@ describe("housekeeping navigation", () => {
labelKey: "pages.housekeeping.domains.economy.title",
descriptionKey: "pages.housekeeping.domains.economy.description",
iconId: "gem",
canonicalHref: "/ase/economy",
previewHref: "/admin-next/economy",
capability: anyCapability(PERMS.CATALOG_VIEW),
routes: [],
searchProviders: [],
@@ -53,7 +53,7 @@ describe("housekeeping navigation", () => {
expect(navigation).toEqual([
{
id: "people",
href: "/ase/people",
href: "/admin-next/people",
iconId: "users",
label: "pages.housekeeping.domains.people.title",
description: "pages.housekeeping.domains.people.description",
@@ -69,19 +69,19 @@ describe("housekeeping navigation", () => {
labelKey: "people.title",
descriptionKey: "people.description",
iconId: "users",
canonicalHref: "/ase/people",
previewHref: "/admin-next/people",
capability: anyCapability(PERMS.USERS_VIEW),
routes: [
{
id: "users",
labelKey: "people.users",
href: "/ase/people/users",
href: "/admin-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW),
},
{
id: "bans",
labelKey: "people.bans",
href: "/ase/people/bans",
href: "/admin-next/people/bans",
capability: anyCapability(PERMS.BANS_VIEW),
},
],
@@ -94,7 +94,7 @@ describe("housekeeping navigation", () => {
labelKey: "system.title",
descriptionKey: "system.description",
iconId: "settings",
canonicalHref: "/ase/system",
previewHref: "/admin-next/system",
capability: anyCapability(PERMS.SETTINGS_VIEW),
routes: [],
searchProviders: [],
@@ -113,14 +113,14 @@ describe("housekeeping navigation", () => {
expect(navigation).toEqual([
{
id: "people",
href: "/ase/people",
href: "/admin-next/people",
iconId: "users",
label: "translated:people.title",
description: "translated:people.description",
items: [
{
id: "users",
href: "/ase/people/users",
href: "/admin-next/people/users",
label: "translated:people.users",
},
],
@@ -167,37 +167,3 @@ describe("housekeeping navigation", () => {
}
});
});
it("keeps navigation hrefs on the canonical surface", () => {
const registry = createHousekeepingRegistry([
{
id: "people",
labelKey: "people.title",
descriptionKey: "people.description",
iconId: "users",
canonicalHref: "/ase/people",
capability: anyCapability(PERMS.USERS_VIEW),
routes: [
{
id: "users",
labelKey: "people.users",
href: "/ase/people/users",
capability: anyCapability(PERMS.USERS_VIEW),
},
],
searchProviders: [],
inboxSources: [],
widgets: [],
},
]);
const navigation = buildHousekeepingNavigation(
registry,
context([PERMS.USERS_VIEW]),
(key) => key,
);
expect(navigation[0]).toMatchObject({
href: "/ase/people",
items: [{ href: "/ase/people/users" }],
});
});
@@ -5,7 +5,6 @@ import type {
HousekeepingDomainManifest,
} from "./contracts";
import type { HousekeepingRegistry } from "./registry";
import { toHousekeepingHref } from "./routing/href";
export interface HousekeepingNavigationDomain {
id: HousekeepingDomainId;
@@ -25,7 +24,7 @@ export function buildHousekeepingNavigation(
.filter((domain) => satisfiesCapability(context, domain.capability))
.map((domain) => ({
id: domain.id,
href: toHousekeepingHref(domain.canonicalHref),
href: domain.previewHref,
iconId: domain.iconId,
label: translate(domain.labelKey),
description: translate(domain.descriptionKey),
@@ -33,7 +32,7 @@ export function buildHousekeepingNavigation(
.filter((route) => satisfiesCapability(context, route.capability))
.map((route) => ({
id: route.id,
href: toHousekeepingHref(route.href),
href: route.href,
label: translate(route.labelKey),
})),
}));
@@ -16,16 +16,10 @@ export function HousekeepingPageShell({
children,
}: HousekeepingPageShellProps) {
return (
<section
aria-labelledby="housekeeping-page-title"
className="min-w-0 max-w-full space-y-6"
>
<section className="space-y-6">
<header className="flex flex-wrap items-start justify-between gap-4 border-b border-[var(--admin-border)] pb-4">
<div className="min-w-0 space-y-1">
<h1
id="housekeeping-page-title"
className="text-xl font-semibold text-[var(--admin-text)]"
>
<h1 className="text-xl font-semibold text-[var(--admin-text)]">
{title}
</h1>
<p className="text-sm text-[var(--admin-text-muted)]">
@@ -35,7 +29,7 @@ export function HousekeepingPageShell({
</div>
{primaryAction ? <div>{primaryAction}</div> : null}
</header>
<div className="min-w-0 max-w-full">{children}</div>
<div>{children}</div>
</section>
);
}
@@ -11,8 +11,6 @@ describe("HousekeepingPageState", () => {
["empty", "status"],
["partial", "status"],
["error", "alert"],
["forbidden", "alert"],
["ready", "status"],
] as const)("renders %s with the %s role", (state, role) => {
const pageState =
state === "partial" ? (
@@ -36,35 +34,6 @@ describe("HousekeepingPageState", () => {
expect(html).toContain(`>${state} description<`);
});
it.each([
["loading", "polite"],
["empty", "polite"],
["partial", "polite"],
["error", "assertive"],
["forbidden", "assertive"],
["ready", "polite"],
] as const)("announces %s changes with %s priority", (state, live) => {
const pageState =
state === "partial" ? (
<HousekeepingPageState
state="partial"
partialLabel="Partial"
title={`${state} title`}
description={`${state} description`}
/>
) : (
<HousekeepingPageState
state={state}
title={`${state} title`}
description={`${state} description`}
/>
);
const html = renderToStaticMarkup(pageState);
expect(html).toContain(`data-page-state="${state}"`);
expect(html).toContain(`aria-live="${live}"`);
});
it("announces loading politely", () => {
const loading = renderToStaticMarkup(
<HousekeepingPageState
@@ -106,12 +75,12 @@ describe("HousekeepingPageState", () => {
state="error"
title="Could not load"
description="Try again later"
retryAction={<a href="/ase/operations">Retry preview</a>}
retryAction={<a href="/admin-next/operations">Retry preview</a>}
/>,
);
expect(html).toContain('role="alert"');
expect(html).toContain('href="/ase/operations"');
expect(html).toContain('href="/admin-next/operations"');
expect(html).toContain(">Retry preview<");
});
});
@@ -123,7 +92,7 @@ describe("HousekeepingPageShell", () => {
title="People"
description="Review operator-facing people data"
context={<span>Preview context</span>}
primaryAction={<a href="/ase/people/new">Create preview</a>}
primaryAction={<a href="/admin-next/people/new">Create preview</a>}
>
<p>Page body</p>
</HousekeepingPageShell>,
@@ -133,7 +102,7 @@ describe("HousekeepingPageShell", () => {
expect(html).toContain(">People<");
expect(html).toContain(">Review operator-facing people data<");
expect(html).toContain(">Preview context<");
expect(html).toContain('href="/ase/people/new"');
expect(html).toContain('href="/admin-next/people/new"');
expect(html).toContain(">Create preview<");
expect(html).toContain(">Page body</p>");
});
@@ -6,21 +6,10 @@ interface HousekeepingPageStateBaseProps {
retryAction?: ReactNode;
}
export type HousekeepingPageState =
| "loading"
| "empty"
| "partial"
| "error"
| "forbidden"
| "ready";
type HousekeepingPageStateProps = HousekeepingPageStateBaseProps &
(
| { state: "partial"; partialLabel: string }
| {
state: Exclude<HousekeepingPageState, "partial">;
partialLabel?: never;
}
| { state: "loading" | "empty" | "error"; partialLabel?: never }
);
export function HousekeepingPageState({
@@ -30,14 +19,13 @@ export function HousekeepingPageState({
description,
retryAction,
}: HousekeepingPageStateProps) {
const isError = state === "error" || state === "forbidden";
const isError = state === "error";
const isPartial = state === "partial";
return (
<section
role={isError ? "alert" : "status"}
aria-live={isError ? "assertive" : "polite"}
data-page-state={state}
aria-live={state === "loading" ? "polite" : undefined}
className={`rounded-lg border p-4 ${
isError
? "border-[var(--admin-error)] bg-[var(--admin-surface)]"
@@ -1,122 +0,0 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
const projectRoot = resolve(import.meta.dirname, "../../../..");
const migrationPath = resolve(
projectRoot,
"drizzle/migrations/0023_housekeeping.sql",
);
const schemaPath = resolve(projectRoot, "src/db/schema.ts");
const legacyAuditColumns = [
"action",
"target",
"target_id",
"details",
"before",
"after",
"diff",
"ip_address",
"created_at",
"updated_at",
] as const;
function migrationSource(): string {
return readFileSync(migrationPath, "utf8");
}
function schemaSource(): string {
return readFileSync(schemaPath, "utf8");
}
describe("housekeeping persistence contract", () => {
it("adds only the approved nullable audit columns and correlation index", () => {
const migration = migrationSource();
const auditAlter = migration.match(
/ALTER TABLE `admin_audit_log`[\s\S]*?;/,
)?.[0];
expect(auditAlter).toBeDefined();
expect(auditAlter).toContain(
"ADD COLUMN `correlation_id` VARCHAR(64) NULL",
);
expect(auditAlter).toContain("ADD COLUMN `outcome` VARCHAR(32) NULL");
expect(auditAlter).toContain("ADD COLUMN `reason` TEXT NULL");
expect(auditAlter).toContain("ADD COLUMN `domain` VARCHAR(32) NULL");
expect(auditAlter).toContain(
"ADD INDEX `admin_audit_log_correlation_id_idx` (`correlation_id`)",
);
expect(auditAlter).not.toMatch(/\b(?:DROP|RENAME|MODIFY|CHANGE)\b/i);
for (const column of legacyAuditColumns) {
expect(auditAlter).not.toMatch(
new RegExp(
`\\b(?:ADD|DROP|RENAME|MODIFY|CHANGE)\\s+COLUMN\\s+\\\`${column}\\\``,
"i",
),
);
}
});
it("creates the exact user-scoped preference table with one primary key", () => {
const migration = migrationSource();
const preferenceTable = migration.match(
/CREATE TABLE `housekeeping_user_preferences` \([\s\S]*?\);/,
)?.[0];
expect(preferenceTable).toBeDefined();
expect(preferenceTable).toContain("`user_id` INT NOT NULL");
expect(preferenceTable).toContain(
"`schema_version` INT NOT NULL DEFAULT 1",
);
expect(preferenceTable).toContain("`payload` LONGTEXT NOT NULL");
expect(preferenceTable).toContain(
"`created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP",
);
expect(preferenceTable).toContain(
"`updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",
);
expect(preferenceTable?.match(/PRIMARY KEY/g)).toHaveLength(1);
expect(preferenceTable).toContain("PRIMARY KEY (`user_id`)");
});
it("declares matching additive Drizzle tables and the published preference row type", () => {
const schema = schemaSource();
expect(schema).toContain("export const AdminAuditLog = mysqlTable(");
expect(schema).toContain('"admin_audit_log"');
expect(schema).toContain(
'correlationId: varchar("correlation_id", { length: 64 })',
);
expect(schema).toContain('outcome: varchar("outcome", { length: 32 })');
expect(schema).toContain('reason: text("reason")');
expect(schema).toContain('domain: varchar("domain", { length: 32 })');
expect(schema).toContain(
'index("admin_audit_log_correlation_id_idx").on(t.correlationId)',
);
expect(schema).toContain(
"export const HousekeepingUserPreferences = mysqlTable(",
);
expect(schema).toContain('"housekeeping_user_preferences"');
expect(schema).toContain('userId: int("user_id").primaryKey().notNull()');
expect(schema).toContain(
'schemaVersion: int("schema_version").notNull().default(1)',
);
expect(schema).toContain('payload: longtext("payload").notNull()');
expect(schema).toContain(
'createdAt: datetime("created_at").notNull().default(sql`CURRENT_TIMESTAMP`)',
);
expect(schema).toContain(
'updatedAt: datetime("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`)',
);
expect(schema).toContain("export type HousekeepingUserPreferenceRow =");
expect(schema).toContain(
"typeof HousekeepingUserPreferences.$inferSelect;",
);
});
it("contains no destructive DDL", () => {
const migration = migrationSource();
expect(migration).not.toMatch(/\b(?:DROP|RENAME)\b/i);
});
});
@@ -1,123 +0,0 @@
import { describe, expect, it } from "vitest";
import {
anyCapability,
type HousekeepingCapabilityContext,
} from "../contracts";
import type { HousekeepingRegistry } from "../registry";
import { reconcilePreferences } from "./reconcile";
import { defaultHousekeepingPreferences } from "./schema";
const usersView = anyCapability("admin.users.view");
const ticketsView = anyCapability("admin.tickets.view");
const bansView = anyCapability("admin.bans.view");
const registry: HousekeepingRegistry = {
domains: [
{
id: "people",
labelKey: "people",
descriptionKey: "people.description",
iconId: "users",
canonicalHref: "/ase/people",
capability: usersView,
routes: [
{
id: "people.users",
labelKey: "users",
href: "/ase/people/users",
capability: usersView,
},
{
id: "people.tickets",
labelKey: "tickets",
href: "/ase/people/tickets",
capability: ticketsView,
},
{
id: "people.bans",
labelKey: "bans",
href: "/ase/people/bans",
capability: bansView,
},
],
searchProviders: [],
inboxSources: [],
widgets: [
{
id: "people.summary",
owner: "people",
capability: usersView,
kind: "mandatory",
load: async () => ({ ok: true, data: null, correlationId: "widget" }),
},
{
id: "people.queue",
owner: "people",
capability: ticketsView,
kind: "optional",
load: async () => ({ ok: true, data: null, correlationId: "widget" }),
},
{
id: "people.bans",
owner: "people",
capability: bansView,
kind: "optional",
load: async () => ({ ok: true, data: null, correlationId: "widget" }),
},
],
},
],
};
const context: HousekeepingCapabilityContext = {
actor: { id: 42, username: "operator", rank: 0 },
isSuperAdmin: false,
has: (slug) => slug === "admin.users.view" || slug === "admin.tickets.view",
hasAny: (...slugs) => slugs.some(context.has),
hasAll: (...slugs) => slugs.every(context.has),
};
describe("reconcilePreferences", () => {
it("drops unknown and unauthorized IDs before returning preferences", () => {
const stored = {
...defaultHousekeepingPreferences(),
pinnedRouteIds: ["removed.route", "people.bans", "people.users"],
pinnedCommandIds: ["removed.command"],
shortcutOrder: [
"removed.route",
"people.bans",
"people.tickets",
"people.users",
],
widgetOrder: ["removed.widget", "people.bans", "people.queue"],
enabledOptionalWidgetIds: [
"removed.widget",
"people.bans",
"people.queue",
],
};
expect(reconcilePreferences(stored, registry, context)).toEqual({
...defaultHousekeepingPreferences(),
pinnedRouteIds: ["people.users"],
shortcutOrder: ["people.tickets", "people.users"],
widgetOrder: ["people.queue", "people.summary"],
enabledOptionalWidgetIds: ["people.queue"],
});
});
it("retains mandatory widgets and preserves the relative order of valid entries", () => {
const stored = {
...defaultHousekeepingPreferences(),
pinnedRouteIds: ["people.tickets", "people.users"],
shortcutOrder: ["people.tickets", "people.users"],
widgetOrder: ["people.queue"],
};
expect(reconcilePreferences(stored, registry, context)).toMatchObject({
pinnedRouteIds: ["people.tickets", "people.users"],
shortcutOrder: ["people.tickets", "people.users"],
widgetOrder: ["people.queue", "people.summary"],
});
});
});
@@ -1,68 +0,0 @@
import { satisfiesCapability } from "../capability-context";
import type {
HousekeepingCapabilityContext,
HousekeepingWidgetDefinition,
} from "../contracts";
import type { HousekeepingRegistry } from "../registry";
import type { HousekeepingPreferences } from "./schema";
export function reconcilePreferences(
stored: HousekeepingPreferences,
registry: HousekeepingRegistry,
context: HousekeepingCapabilityContext,
): HousekeepingPreferences {
const routes = registry.domains
.flatMap((domain) => domain.routes)
.filter((route) => satisfiesCapability(context, route.capability));
const widgets = registry.domains
.flatMap((domain) => domain.widgets)
.filter((widget) => satisfiesCapability(context, widget.capability));
const allowedRouteIds = new Set(routes.map((route) => route.id));
const allowedWidgetIds = new Set(widgets.map((widget) => widget.id));
const allowedOptionalWidgetIds = new Set(
widgets
.filter((widget) => widget.kind === "optional")
.map((widget) => widget.id),
);
const mandatoryWidgets = widgets.filter(
(widget): widget is HousekeepingWidgetDefinition & { kind: "mandatory" } =>
widget.kind === "mandatory",
);
const retainedWidgetOrder = filterKnown(stored.widgetOrder, allowedWidgetIds);
return {
schemaVersion: 1,
pinnedRouteIds: filterKnown(stored.pinnedRouteIds, allowedRouteIds),
// Commands are intentionally omitted until the command registry publishes
// a stable registry collection; unregistered IDs are never trusted.
pinnedCommandIds: [],
shortcutOrder: filterKnown(stored.shortcutOrder, allowedRouteIds),
widgetOrder: appendMissing(
retainedWidgetOrder,
mandatoryWidgets.map((widget) => widget.id),
),
enabledOptionalWidgetIds: filterKnown(
stored.enabledOptionalWidgetIds,
allowedOptionalWidgetIds,
),
};
}
function filterKnown(
values: readonly string[],
allowed: ReadonlySet<string>,
): string[] {
return values.filter((value) => allowed.has(value));
}
function appendMissing(
values: readonly string[],
required: readonly string[],
): string[] {
const combined = [...values];
const known = new Set(combined);
for (const value of required) {
if (!known.has(value)) combined.push(value);
}
return combined;
}
@@ -1,140 +0,0 @@
import { describe, expect, it, vi } from "vitest";
const drizzleMocks = vi.hoisted(() => ({
eq: vi.fn((column: unknown, value: unknown) => ({ column, value })),
}));
vi.mock("drizzle-orm", async (importOriginal) => ({
...(await importOriginal<typeof import("drizzle-orm")>()),
eq: drizzleMocks.eq,
}));
import { HousekeepingUserPreferences } from "@/lib/db";
import { createDrizzleHousekeepingPreferencesStorage } from "@/lib/housekeeping-preferences-repository";
import {
createHousekeepingPreferencesRepository,
type HousekeepingPreferencesStorage,
} from "./repository";
import { defaultHousekeepingPreferences } from "./schema";
describe("housekeeping preferences repository", () => {
it("returns a fresh default when no row exists", async () => {
const storage: HousekeepingPreferencesStorage = {
findByUserId: vi.fn().mockResolvedValue(null),
upsert: vi.fn(),
};
const repository = createHousekeepingPreferencesRepository(storage);
expect(await repository.read(42)).toEqual(defaultHousekeepingPreferences());
expect(storage.findByUserId).toHaveBeenCalledWith(42);
});
it("reads validated JSON and writes the schema-versioned payload through the injected adapter", async () => {
const value = {
...defaultHousekeepingPreferences(),
pinnedRouteIds: ["people.users"],
};
const storage: HousekeepingPreferencesStorage = {
findByUserId: vi.fn().mockResolvedValue({
schemaVersion: 1,
payload: JSON.stringify(value),
}),
upsert: vi.fn(),
};
const repository = createHousekeepingPreferencesRepository(storage);
expect(await repository.read(42)).toEqual(value);
await repository.upsert(42, value);
expect(storage.upsert).toHaveBeenCalledWith({
userId: 42,
schemaVersion: 1,
payload: JSON.stringify(value),
});
});
it("returns a fresh default for malformed or incompatible stored values", async () => {
const storage: HousekeepingPreferencesStorage = {
findByUserId: vi
.fn()
.mockResolvedValueOnce({ schemaVersion: 1, payload: "{broken" })
.mockResolvedValueOnce({ schemaVersion: 2, payload: "{}" })
.mockResolvedValueOnce({
schemaVersion: 1,
payload: JSON.stringify({ schemaVersion: 1 }),
}),
upsert: vi.fn(),
};
const repository = createHousekeepingPreferencesRepository(storage);
await expect(repository.read(42)).resolves.toEqual(
defaultHousekeepingPreferences(),
);
await expect(repository.read(42)).resolves.toEqual(
defaultHousekeepingPreferences(),
);
await expect(repository.read(42)).resolves.toEqual(
defaultHousekeepingPreferences(),
);
});
it("preserves genuine storage failures instead of treating them as corrupt preferences", async () => {
const storage: HousekeepingPreferencesStorage = {
findByUserId: vi
.fn()
.mockRejectedValue(new Error("database unavailable")),
upsert: vi.fn(),
};
await expect(
createHousekeepingPreferencesRepository(storage).read(42),
).rejects.toThrow("database unavailable");
});
it("uses the preferences table and user ID for the typed Drizzle read and upsert", async () => {
const limit = vi.fn().mockResolvedValue([]);
const where = vi.fn().mockReturnValue({ limit });
const from = vi.fn().mockReturnValue({ where });
const select = vi.fn().mockReturnValue({ from });
const onDuplicateKeyUpdate = vi.fn().mockResolvedValue(undefined);
const values = vi.fn().mockReturnValue({ onDuplicateKeyUpdate });
const insert = vi.fn().mockReturnValue({ values });
const storage = createDrizzleHousekeepingPreferencesStorage({
select,
insert,
} as never);
const value = defaultHousekeepingPreferences();
await storage.findByUserId(42);
await storage.upsert({
userId: 42,
schemaVersion: 1,
payload: JSON.stringify(value),
});
expect(select).toHaveBeenCalledWith({
schemaVersion: HousekeepingUserPreferences.schemaVersion,
payload: HousekeepingUserPreferences.payload,
});
expect(from).toHaveBeenCalledWith(HousekeepingUserPreferences);
expect(drizzleMocks.eq).toHaveBeenCalledWith(
HousekeepingUserPreferences.userId,
42,
);
expect(where).toHaveBeenCalledWith({
column: HousekeepingUserPreferences.userId,
value: 42,
});
expect(values).toHaveBeenCalledWith({
userId: 42,
schemaVersion: 1,
payload: JSON.stringify(value),
});
expect(onDuplicateKeyUpdate).toHaveBeenCalledWith({
set: expect.objectContaining({
schemaVersion: 1,
payload: JSON.stringify(value),
updatedAt: expect.any(Date),
}),
});
});
});
@@ -1,53 +0,0 @@
import type { HousekeepingPreferences } from "./schema";
import {
defaultHousekeepingPreferences,
parseHousekeepingPreferences,
} from "./schema";
export interface HousekeepingPreferencesStorageRow {
schemaVersion: number;
payload: string;
}
export interface HousekeepingPreferencesStorage {
findByUserId(
userId: number,
): Promise<HousekeepingPreferencesStorageRow | null>;
upsert(row: {
userId: number;
schemaVersion: 1;
payload: string;
}): Promise<void>;
}
export interface HousekeepingPreferencesRepository {
read(userId: number): Promise<HousekeepingPreferences>;
upsert(userId: number, value: HousekeepingPreferences): Promise<void>;
}
export function createHousekeepingPreferencesRepository(
storage: HousekeepingPreferencesStorage,
): HousekeepingPreferencesRepository {
return {
async read(userId) {
const row = await storage.findByUserId(userId);
if (!row) return defaultHousekeepingPreferences();
try {
if (row.schemaVersion !== 1) {
return defaultHousekeepingPreferences();
}
return parseHousekeepingPreferences(row.payload);
} catch {
return defaultHousekeepingPreferences();
}
},
async upsert(userId, value) {
await storage.upsert({
userId,
schemaVersion: value.schemaVersion,
payload: JSON.stringify(value),
});
},
};
}
@@ -1,91 +0,0 @@
import { describe, expect, it } from "vitest";
import {
defaultHousekeepingPreferences,
housekeepingPreferencesSchema,
parseHousekeepingPreferences,
} from "./schema";
describe("housekeeping preferences schema", () => {
it("rejects malformed serialized JSON", () => {
expect(() => parseHousekeepingPreferences("{not-json")).toThrow(
"invalid housekeeping preferences JSON",
);
});
it("rejects unknown keys, duplicate identifiers, and unsupported versions", () => {
const valid = defaultHousekeepingPreferences();
expect(
housekeepingPreferencesSchema.safeParse({ ...valid, unsupported: true })
.success,
).toBe(false);
expect(
housekeepingPreferencesSchema.safeParse({
...valid,
pinnedRouteIds: ["people.users", "people.users"],
}).success,
).toBe(false);
expect(
housekeepingPreferencesSchema.safeParse({ ...valid, schemaVersion: 2 })
.success,
).toBe(false);
});
it("parses the current validated presentation payload", () => {
const value = {
schemaVersion: 1,
pinnedRouteIds: ["people.users"],
pinnedCommandIds: [],
shortcutOrder: ["people.users"],
widgetOrder: ["people.summary"],
enabledOptionalWidgetIds: [],
};
expect(parseHousekeepingPreferences(JSON.stringify(value))).toEqual(value);
});
it("rejects identifiers and lists that exceed the bounded presentation payload", () => {
const value = defaultHousekeepingPreferences();
expect(
housekeepingPreferencesSchema.safeParse({
...value,
pinnedRouteIds: ["a".repeat(129)],
}).success,
).toBe(false);
expect(
housekeepingPreferencesSchema.safeParse({
...value,
pinnedRouteIds: Array.from(
{ length: 65 },
(_, index) => `route.${index}`,
),
}).success,
).toBe(false);
expect(
housekeepingPreferencesSchema.safeParse({
...value,
pinnedRouteIds: Array.from({ length: 33 }, (_, index) =>
`${index}`.padEnd(128, "a"),
),
}).success,
).toBe(false);
});
it("rejects a Unicode payload whose UTF-8 bytes exceed the 4096-byte limit", () => {
const value = {
...defaultHousekeepingPreferences(),
pinnedRouteIds: Array.from(
{ length: 29 },
(_, index) => `${index.toString().padStart(3, "0")}${"é".repeat(125)}`,
),
};
const serialized = JSON.stringify(value);
expect(serialized.length).toBeLessThanOrEqual(4096);
expect(new TextEncoder().encode(serialized).byteLength).toBeGreaterThan(
4096,
);
expect(housekeepingPreferencesSchema.safeParse(value).success).toBe(false);
});
});
@@ -1,80 +0,0 @@
import { z } from "zod";
export const HOUSEKEEPING_PREFERENCE_MAX_IDENTIFIER_LENGTH = 128;
export const HOUSEKEEPING_PREFERENCE_MAX_ITEMS_PER_ARRAY = 64;
export const HOUSEKEEPING_PREFERENCE_MAX_SERIALIZED_BYTES = 4096;
export interface HousekeepingPreferences {
schemaVersion: 1;
pinnedRouteIds: string[];
pinnedCommandIds: string[];
shortcutOrder: string[];
widgetOrder: string[];
enabledOptionalWidgetIds: string[];
}
const uniqueIdentifiers = z
.array(
z.string().trim().min(1).max(HOUSEKEEPING_PREFERENCE_MAX_IDENTIFIER_LENGTH),
)
.max(HOUSEKEEPING_PREFERENCE_MAX_ITEMS_PER_ARRAY)
.superRefine((values, context) => {
const seen = new Set<string>();
for (const [index, value] of values.entries()) {
if (seen.has(value)) {
context.addIssue({
code: "custom",
message: "duplicate preference identifier",
path: [index],
});
}
seen.add(value);
}
});
export const housekeepingPreferencesSchema: z.ZodType<HousekeepingPreferences> =
z
.object({
schemaVersion: z.literal(1),
pinnedRouteIds: uniqueIdentifiers,
pinnedCommandIds: uniqueIdentifiers,
shortcutOrder: uniqueIdentifiers,
widgetOrder: uniqueIdentifiers,
enabledOptionalWidgetIds: uniqueIdentifiers,
})
.strict()
.superRefine((preferences, context) => {
if (
new TextEncoder().encode(JSON.stringify(preferences)).byteLength >
HOUSEKEEPING_PREFERENCE_MAX_SERIALIZED_BYTES
) {
context.addIssue({
code: "custom",
message: "housekeeping preferences payload is too large",
});
}
});
export function defaultHousekeepingPreferences(): HousekeepingPreferences {
return {
schemaVersion: 1,
pinnedRouteIds: [],
pinnedCommandIds: [],
shortcutOrder: [],
widgetOrder: [],
enabledOptionalWidgetIds: [],
};
}
export function parseHousekeepingPreferences(
serialized: string,
): HousekeepingPreferences {
let value: unknown;
try {
value = JSON.parse(serialized);
} catch {
throw new Error("invalid housekeeping preferences JSON");
}
return housekeepingPreferencesSchema.parse(value);
}
@@ -1,29 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import {
anyCapability,
type HousekeepingWidgetDefinition,
ok,
} from "../contracts";
import { toHousekeepingWidgetOptions } from "./widget-options";
describe("toHousekeepingWidgetOptions", () => {
it("projects provider definitions to a serializable client view model", () => {
const load = vi.fn(async () => ok({ count: 1 }, "widget"));
const definitions: readonly HousekeepingWidgetDefinition[] = [
{
id: "people.queue",
owner: "people",
capability: anyCapability("admin.users.view"),
kind: "mandatory",
load,
},
];
const options = toHousekeepingWidgetOptions(definitions);
expect(options).toEqual([{ id: "people.queue", kind: "mandatory" }]);
expect(structuredClone(options)).toEqual(options);
expect(options[0]).not.toHaveProperty("load");
expect(load).not.toHaveBeenCalled();
});
});
@@ -1,15 +0,0 @@
import type {
HousekeepingWidgetDefinition,
HousekeepingWidgetKind,
} from "../contracts";
export interface HousekeepingWidgetOption {
readonly id: string;
readonly kind: HousekeepingWidgetKind;
}
export function toHousekeepingWidgetOptions(
definitions: readonly HousekeepingWidgetDefinition[],
): readonly HousekeepingWidgetOption[] {
return definitions.map(({ id, kind }) => ({ id, kind }));
}
@@ -0,0 +1,58 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { isHousekeepingPreviewEnabled } from "./preview-gate";
describe("isHousekeepingPreviewEnabled", () => {
it.each([
["development", true, true],
["test", true, true],
["development", false, false],
["production", true, false],
["production", false, false],
] as const)("NODE_ENV=%s flag=%s => %s", (nodeEnv, flag, expected) => {
expect(isHousekeepingPreviewEnabled({ nodeEnv, flag })).toBe(expected);
});
});
describe("HOUSEKEEPING_NEXT_PREVIEW_ENABLED", () => {
const originalSkipValidation = process.env.SKIP_ENV_VALIDATION;
const originalPreviewFlag = process.env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED;
beforeEach(() => {
vi.resetModules();
delete process.env.SKIP_ENV_VALIDATION;
});
afterEach(() => {
if (originalSkipValidation === undefined) {
delete process.env.SKIP_ENV_VALIDATION;
} else {
process.env.SKIP_ENV_VALIDATION = originalSkipValidation;
}
if (originalPreviewFlag === undefined) {
delete process.env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED;
} else {
process.env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED = originalPreviewFlag;
}
vi.resetModules();
});
it.each([
["true", true],
["1", true],
["false", false],
["yes", false],
[undefined, false],
] as const)("normalizes %s to %s", async (value, expected) => {
if (value === undefined) {
delete process.env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED;
} else {
process.env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED = value;
}
const { env } = await import("@/env");
expect(env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED).toBe(expected);
});
});
@@ -0,0 +1,6 @@
export function isHousekeepingPreviewEnabled(input: {
nodeEnv: "development" | "test" | "production";
flag: boolean;
}): boolean {
return input.nodeEnv !== "production" && input.flag;
}
@@ -9,146 +9,16 @@ import type { HousekeepingCapabilityContext } from "./contracts";
const routeMocks = vi.hoisted(() => {
const messages: Record<string, string> = {
"preview.badge": "HK::preview-badge",
"preview.commandDisabled": "HK::command-disabled",
"preview.backToSite": "HK::back-to-site",
"navigation.skipToContent": "HK::skip-to-content",
"navigation.primary": "HK::primary-navigation",
"navigation.contextual": "HK::contextual-navigation",
"navigation.operatorRank": "HK::operator-rank",
"commandDeck.placeholder": "HK::command-placeholder",
"commandDeck.navigation": "HK::command-navigation",
"commandDeck.commands": "HK::command-commands",
"commandDeck.entities": "HK::command-entities",
"commandDeck.loading": "HK::command-loading",
"commandDeck.empty": "HK::command-empty",
"commandDeck.partial": "HK::command-partial",
"commandDeck.close": "HK::command-close",
"domains.people.title": "HK::people-title",
"domains.people.description": "Localized People description",
"domains.economy.title": "Localized Economy",
"domains.economy.description": "Localized Economy description",
"domains.operations.title": "Localized Operations",
"domains.operations.description": "Localized Operations description",
"domains.content.title": "Localized Content",
"domains.content.description": "Localized Content description",
"domains.hotel.title": "Localized Hotel",
"domains.hotel.description": "Localized Hotel description",
"domains.system.title": "HK::system-title",
"domains.system.description": "Localized System description",
"routes.content.editorial.articles": "HK::content-editorial-articles",
"routes.content.editorial.article-create":
"HK::content-editorial-article-create",
"routes.content.editorial.article-detail":
"HK::content-editorial-article-detail",
"routes.content.media.photos": "HK::content-media-photos",
"routes.content.media.library": "HK::content-media-library",
"routes.content.media.banners": "HK::content-media-banners",
"routes.content.media.ads": "HK::content-media-ads",
"routes.content.media.ad-create": "HK::content-media-ad-create",
"routes.content.media.ad-detail": "HK::content-media-ad-detail",
"routes.content.engagement.events": "HK::content-engagement-events",
"routes.content.engagement.event-create":
"HK::content-engagement-event-create",
"routes.content.engagement.event-types":
"HK::content-engagement-event-types",
"routes.content.engagement.event-detail":
"HK::content-engagement-event-detail",
"routes.content.engagement.polls": "HK::content-engagement-polls",
"routes.content.engagement.poll-create":
"HK::content-engagement-poll-create",
"routes.content.engagement.poll-detail":
"HK::content-engagement-poll-detail",
"routes.content.help.questions": "HK::content-help-questions",
"routes.content.help.question-create": "HK::content-help-question-create",
"routes.content.help.question-detail": "HK::content-help-question-detail",
"routes.content.editorial.tags": "HK::content-editorial-tags",
"routes.content.engagement.prefixes": "HK::content-engagement-prefixes",
"routes.content.editorial.writeable-boxes":
"HK::content-editorial-writeable-boxes",
"routes.content.help.email-templates": "HK::content-help-email-templates",
"routes.content.brand.theme": "HK::content-brand-theme",
"routes.content.brand.favicon": "HK::content-brand-favicon",
"routes.content.localization.overview": "HK::content-localization-overview",
"routes.content.localization.client": "HK::content-localization-client",
"routes.content.localization.cms": "HK::content-localization-cms",
"routes.content.localization.emulator": "HK::content-localization-emulator",
"routes.system.access.permissions": "HK::system-access-permissions",
"routes.system.access.permission-detail":
"HK::system-access-permission-detail",
"routes.system.configuration.settings": "HK::system-configuration-settings",
"routes.system.configuration.emulator": "HK::system-configuration-emulator",
"routes.system.observability.analytics":
"HK::system-observability-analytics",
"routes.system.observability.analytics-activity":
"HK::system-observability-analytics-activity",
"routes.system.observability.analytics-economy":
"HK::system-observability-analytics-economy",
"routes.system.observability.devops": "HK::system-observability-devops",
"routes.system.observability.devops-errors":
"HK::system-observability-devops-errors",
"routes.system.observability.logs-staff":
"HK::system-observability-logs-staff",
"routes.system.observability.logs-audit":
"HK::system-observability-logs-audit",
"routes.system.observability.logs-chat":
"HK::system-observability-logs-chat",
"routes.system.observability.logs-commands":
"HK::system-observability-logs-commands",
"routes.system.observability.logs-trades":
"HK::system-observability-logs-trades",
"routes.system.operations.alerts": "HK::system-operations-alerts",
"routes.system.operations.command-center":
"HK::system-operations-command-center",
"routes.system.operations.maintenance": "HK::system-operations-maintenance",
"routes.economy.catalog.overview": "HK::economy-catalog-overview",
"routes.economy.catalog.detail": "HK::economy-catalog-detail",
"routes.economy.catalog.builder-club-detail":
"HK::economy-catalog-builder-club-detail",
"routes.economy.catalog.maintenance": "HK::economy-catalog-maintenance",
"routes.economy.items.overview": "HK::economy-items-overview",
"routes.economy.items.detail": "HK::economy-items-detail",
"routes.economy.commerce.shop": "HK::economy-commerce-shop",
"routes.economy.commerce.shop-create": "HK::economy-commerce-shop-create",
"routes.economy.commerce.shop-detail": "HK::economy-commerce-shop-detail",
"routes.economy.commerce.marketplace": "HK::economy-commerce-marketplace",
"routes.economy.commerce.vouchers": "HK::economy-commerce-vouchers",
"routes.economy.commerce.subscriptions":
"HK::economy-commerce-subscriptions",
"routes.economy.history.transactions": "HK::economy-history-transactions",
"routes.economy.value.rare-values": "HK::economy-value-rare-values",
"routes.economy.rewards.badges": "HK::economy-rewards-badges",
"routes.economy.rewards.achievements": "HK::economy-rewards-achievements",
"routes.economy.rewards.sounds": "HK::economy-rewards-sounds",
"routes.economy.rewards.calendar": "HK::economy-rewards-calendar",
"routes.economy.rewards.calendar-detail":
"HK::economy-rewards-calendar-detail",
"routes.hotel.rooms": "HK::hotel-rooms",
"routes.hotel.room-detail": "HK::hotel-room-detail",
"routes.hotel.room-furni": "HK::hotel-room-furni",
"routes.hotel.navigation": "HK::hotel-navigation",
"routes.hotel.radio.overview": "HK::hotel-radio-overview",
"routes.hotel.radio.settings": "HK::hotel-radio-settings",
"routes.hotel.radio.monitoring": "HK::hotel-radio-monitoring",
"routes.hotel.radio.moderation": "HK::hotel-radio-moderation",
"routes.hotel.radio.autodj": "HK::hotel-radio-autodj",
"routes.hotel.radio.history": "HK::hotel-radio-history",
"routes.hotel.radio.points": "HK::hotel-radio-points",
"routes.hotel.radio.ranks": "HK::hotel-radio-ranks",
"routes.hotel.radio.api-keys": "HK::hotel-radio-api-keys",
"routes.hotel.radio.banners": "HK::hotel-radio-banners",
"routes.hotel.radio.embed": "HK::hotel-radio-embed",
"routes.hotel.studio.audit": "HK::hotel-studio-audit",
"routes.hotel.studio.badges": "HK::hotel-studio-badges",
"routes.hotel.studio.clone": "HK::hotel-studio-clone",
"routes.hotel.studio.clothing": "HK::hotel-studio-clothing",
"routes.hotel.studio.effects": "HK::hotel-studio-effects",
"routes.hotel.studio.furni": "HK::hotel-studio-furni",
"routes.hotel.studio.maintenance": "HK::hotel-studio-maintenance",
"routes.hotel.studio.pets": "HK::hotel-studio-pets",
"routes.hotel.studio.repair-icons": "HK::hotel-studio-repair-icons",
"routes.hotel.studio.sync": "HK::hotel-studio-sync",
"routes.hotel.studio.upload": "HK::hotel-studio-upload",
"routes.operations.workspace": "HK::operations-workspace",
"states.empty.title": "Localized empty title",
"states.empty.description": "Localized empty description",
};
@@ -160,6 +30,10 @@ const routeMocks = vi.hoisted(() => {
});
return {
env: {
NODE_ENV: "test" as "development" | "test" | "production",
HOUSEKEEPING_NEXT_PREVIEW_ENABLED: true,
},
getHousekeepingCapabilityContext: vi.fn(),
getTranslations: vi.fn(async (namespace: string) => {
if (namespace !== "pages.housekeeping") {
@@ -173,17 +47,11 @@ const routeMocks = vi.hoisted(() => {
redirect: vi.fn((href: string): never => {
throw new Error(`NEXT_REDIRECT:${href}`);
}),
renderHousekeepingRoute: vi.fn(
async (input: {
context: { actor: { username: string } };
match: { routeId: string };
}) =>
`Rendered ${input.match.routeId} for ${input.context.actor.username}`,
),
translate,
};
});
vi.mock("@/env", () => ({ env: routeMocks.env }));
vi.mock("next/navigation", () => ({
notFound: routeMocks.notFound,
redirect: routeMocks.redirect,
@@ -195,114 +63,31 @@ vi.mock("@/features/housekeeping/foundation/server-capability-context", () => ({
getHousekeepingCapabilityContext: routeMocks.getHousekeepingCapabilityContext,
}));
vi.mock("@/lib/db", () => {
throw new Error("route entrypoints must not import the database");
throw new Error("preview routes must not import the database");
});
vi.mock("@/lib/auth", () => {
throw new Error("route entrypoints must not call auth directly");
throw new Error("preview routes must not call auth directly");
});
vi.mock("@/lib/permissions", () => {
throw new Error("route entrypoints must not reload permissions directly");
throw new Error("preview routes must not reload permissions directly");
});
vi.mock("@/actions", () => {
throw new Error("route entrypoints must not import actions");
throw new Error("preview routes must not import actions");
});
vi.mock("@/app/actions", () => {
throw new Error("route entrypoints must not import actions");
throw new Error("preview routes must not import actions");
});
vi.mock("@/features/housekeeping/manifests", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/features/housekeeping/manifests")>();
return {
HOUSEKEEPING_MANIFESTS: actual.HOUSEKEEPING_MANIFESTS.map((manifest) =>
manifest.id === "people"
? {
...manifest,
routes: [
{
id: "people.users",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/users" as const,
capability: {
mode: "any" as const,
slugs: ["admin.users.view", "mod.cfh.view"],
},
},
{
id: "people.user-detail",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/users/:id" as const,
capability: {
mode: "any" as const,
slugs: ["admin.users.view", "mod.cfh.view"],
},
},
{
id: "people.bans",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/bans" as const,
capability: {
mode: "any" as const,
slugs: ["admin.bans.view"],
},
},
{
id: "people.tool-detail",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/tools/:id" as const,
capability: {
mode: "any" as const,
slugs: ["admin.users.view"],
},
},
{
id: "people.literal-tool",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/tools/a+b[1]" as const,
capability: {
mode: "any" as const,
slugs: ["admin.users.view"],
},
},
],
}
: manifest,
),
};
});
vi.mock("@/features/housekeeping/route-handlers", () => ({
HOUSEKEEPING_ROUTE_HANDLERS: [
{
routeId: "operations.workspace",
render: routeMocks.renderHousekeepingRoute,
},
{ routeId: "people.users", render: routeMocks.renderHousekeepingRoute },
{
routeId: "people.user-detail",
render: routeMocks.renderHousekeepingRoute,
},
{ routeId: "people.bans", render: routeMocks.renderHousekeepingRoute },
{
routeId: "people.tool-detail",
render: routeMocks.renderHousekeepingRoute,
},
{
routeId: "people.literal-tool",
render: routeMocks.renderHousekeepingRoute,
},
],
}));
import HousekeepingDomainPage from "@/app/ase/[domain]/[[...segments]]/page";
import HousekeepingDomainLayout from "@/app/ase/[domain]/layout";
import HousekeepingLayout from "@/app/ase/layout";
import HousekeepingPage from "@/app/ase/page";
import AdminNextDomainLayout from "@/app/admin-next/[domain]/layout";
import AdminNextDomainPage from "@/app/admin-next/[domain]/page";
import AdminNextLayout from "@/app/admin-next/layout";
import AdminNextPage from "@/app/admin-next/page";
const routeFiles = [
"src/app/ase/layout.tsx",
"src/app/ase/page.tsx",
"src/app/ase/[domain]/layout.tsx",
"src/app/ase/[domain]/[[...segments]]/page.tsx",
"src/app/admin-next/layout.tsx",
"src/app/admin-next/page.tsx",
"src/app/admin-next/[domain]/layout.tsx",
"src/app/admin-next/[domain]/page.tsx",
] as const;
const forbiddenModuleRoots = [
@@ -575,42 +360,66 @@ async function renderRoute(route: ReactNode | Promise<ReactNode>) {
return renderToStaticMarkup(await route);
}
describe("/ase canonical layout", () => {
it("renders without a runtime preview gate", async () => {
const html = await renderRoute(
HousekeepingLayout({
children: createElement("p", null, "Canonical child"),
}),
);
expect(html).toContain("Canonical child");
expect(routeMocks.notFound).not.toHaveBeenCalled();
describe("/admin-next preview gate", () => {
beforeEach(() => {
vi.clearAllMocks();
routeMocks.env.NODE_ENV = "test";
routeMocks.env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED = true;
});
it.each([
["production", true],
["production", false],
["development", false],
] as const)("returns 404 for NODE_ENV=%s flag=%s", async (nodeEnv, flag) => {
routeMocks.env.NODE_ENV = nodeEnv;
routeMocks.env.HOUSEKEEPING_NEXT_PREVIEW_ENABLED = flag;
await expect(async () =>
renderRoute(
AdminNextLayout({
children: createElement("p", null, "Preview child"),
}),
),
).rejects.toThrow("NEXT_NOT_FOUND");
expect(routeMocks.notFound).toHaveBeenCalledTimes(1);
});
it.each(["development", "test"] as const)(
"renders children in %s when explicitly enabled",
async (nodeEnv) => {
routeMocks.env.NODE_ENV = nodeEnv;
const html = await renderRoute(
AdminNextLayout({
children: createElement("p", null, "Preview child"),
}),
);
expect(html).toContain("Preview child");
expect(routeMocks.notFound).not.toHaveBeenCalled();
},
);
});
describe("/ase operational home", () => {
describe("/admin-next first visible domain", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("renders the Operations workspace directly for an administrator", async () => {
it("redirects an administrator to Operations in locked registry order", async () => {
routeMocks.getHousekeepingCapabilityContext.mockResolvedValue(
capabilityContext([PERMS.ADMIN_DASHBOARD, PERMS.USERS_VIEW]),
);
const html = await renderRoute(HousekeepingPage());
expect(html).toContain("Rendered operations.workspace");
expect(html).toContain("HK::operations-workspace");
expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledWith(
expect.objectContaining({
match: expect.objectContaining({ routeId: "operations.workspace" }),
}),
await expect(AdminNextPage()).rejects.toThrow(
"NEXT_REDIRECT:/admin-next/operations",
);
expect(routeMocks.redirect).not.toHaveBeenCalled();
expect(routeMocks.redirect).toHaveBeenCalledWith("/admin-next/operations");
expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes(
1,
);
expect(routeMocks.getTranslations).toHaveBeenCalledTimes(1);
expect(routeMocks.getTranslations).not.toHaveBeenCalled();
});
it("redirects a moderator with only an approved mod view capability to People", async () => {
@@ -618,10 +427,10 @@ describe("/ase operational home", () => {
capabilityContext([PERMS.MOD_CFH_VIEW]),
);
await expect(HousekeepingPage()).rejects.toThrow(
"NEXT_REDIRECT:/ase/people/users",
await expect(AdminNextPage()).rejects.toThrow(
"NEXT_REDIRECT:/admin-next/people",
);
expect(routeMocks.redirect).toHaveBeenCalledWith("/ase/people/users");
expect(routeMocks.redirect).toHaveBeenCalledWith("/admin-next/people");
expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes(
1,
);
@@ -632,7 +441,7 @@ describe("/ase operational home", () => {
capabilityContext([]),
);
await expect(HousekeepingPage()).rejects.toThrow("NEXT_NOT_FOUND");
await expect(AdminNextPage()).rejects.toThrow("NEXT_NOT_FOUND");
expect(routeMocks.notFound).toHaveBeenCalledTimes(1);
expect(routeMocks.redirect).not.toHaveBeenCalled();
expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes(
@@ -641,14 +450,14 @@ describe("/ase operational home", () => {
});
});
describe("/ase/[domain] layout", () => {
describe("/admin-next/[domain] layout", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("rejects an unknown domain before loading capability context", async () => {
await expect(
HousekeepingDomainLayout({
AdminNextDomainLayout({
children: createElement("p", null, "Unknown body"),
params: Promise.resolve({ domain: "unknown" }),
}),
@@ -663,7 +472,7 @@ describe("/ase/[domain] layout", () => {
);
await expect(
HousekeepingDomainLayout({
AdminNextDomainLayout({
children: createElement("p", null, "Economy body"),
params: Promise.resolve({ domain: "economy" }),
}),
@@ -680,7 +489,7 @@ describe("/ase/[domain] layout", () => {
);
const html = await renderRoute(
HousekeepingDomainLayout({
AdminNextDomainLayout({
children: createElement("p", null, "People body"),
params: Promise.resolve({ domain: "people" }),
}),
@@ -691,7 +500,7 @@ describe("/ase/[domain] layout", () => {
expect(html).toContain("HK::primary-navigation");
expect(html).toContain("HK::contextual-navigation");
expect(html).toContain("HK::command-disabled");
expect(html).not.toContain("HK::preview-badge");
expect(html).toContain("HK::preview-badge");
expect(html).toContain("HK::back-to-site");
expect(html).toContain("HK::people-title");
expect(html).toContain("People body");
@@ -704,153 +513,36 @@ describe("/ase/[domain] layout", () => {
);
expect(routeMocks.getTranslations).toHaveBeenCalledTimes(1);
});
it("builds and translates the complete real System navigation", async () => {
routeMocks.getHousekeepingCapabilityContext.mockResolvedValue({
...capabilityContext([]),
isSuperAdmin: true,
});
const html = await renderRoute(
HousekeepingDomainLayout({
children: createElement("p", null, "System body"),
params: Promise.resolve({ domain: "system" }),
}),
);
for (const label of [
"HK::system-access-permissions",
"HK::system-access-permission-detail",
"HK::system-configuration-settings",
"HK::system-configuration-emulator",
"HK::system-observability-analytics",
"HK::system-observability-analytics-activity",
"HK::system-observability-analytics-economy",
"HK::system-observability-devops",
"HK::system-observability-devops-errors",
"HK::system-observability-logs-staff",
"HK::system-observability-logs-audit",
"HK::system-observability-logs-chat",
"HK::system-observability-logs-commands",
"HK::system-observability-logs-trades",
"HK::system-operations-alerts",
"HK::system-operations-command-center",
"HK::system-operations-maintenance",
]) {
expect(html).toContain(label);
}
expect(html).toContain("HK::system-title");
expect(html).toContain("System body");
});
});
describe("/ase/[domain]/[[...segments]] page", () => {
describe("/admin-next/[domain] page", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("maps a permitted canonical path and renders its handler with one context", async () => {
const context = capabilityContext([PERMS.MOD_CFH_VIEW]);
routeMocks.getHousekeepingCapabilityContext.mockResolvedValue(context);
it("renders the real localized manifest and empty state without reloading access", async () => {
const html = await renderRoute(
HousekeepingDomainPage({
params: Promise.resolve({ domain: "people", segments: ["users"] }),
AdminNextDomainPage({
params: Promise.resolve({ domain: "people" }),
}),
);
expect(html).toContain("Rendered people.users for refreshed-moderator");
expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes(
1,
);
expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledTimes(1);
expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledWith({
context,
translate: expect.any(Function),
match: {
routeId: "people.users",
domain: "people",
params: {},
canonicalHref: "/ase/people/users",
},
});
expect(routeMocks.getTranslations).toHaveBeenCalledOnce();
expect(html).toContain("HK::people-title");
expect(html).toContain("Localized People description");
expect(html).toContain("Localized empty title");
expect(html).toContain("Localized empty description");
expect(routeMocks.getHousekeepingCapabilityContext).not.toHaveBeenCalled();
expect(routeMocks.getTranslations).toHaveBeenCalledTimes(1);
});
it("maps nested canonical segments to canonical dynamic parameters", async () => {
const context = capabilityContext([PERMS.USERS_VIEW]);
routeMocks.getHousekeepingCapabilityContext.mockResolvedValue(context);
await renderRoute(
HousekeepingDomainPage({
params: Promise.resolve({
domain: "people",
segments: ["users", "42"],
}),
}),
);
expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledWith({
context,
translate: expect.any(Function),
match: {
routeId: "people.user-detail",
domain: "people",
params: { id: "42" },
canonicalHref: "/ase/people/users/42",
},
});
});
it("keeps catch-all encoding from changing the literal route handler", async () => {
const context = capabilityContext([PERMS.USERS_VIEW]);
routeMocks.getHousekeepingCapabilityContext.mockResolvedValue(context);
await renderRoute(
HousekeepingDomainPage({
params: Promise.resolve({
domain: "people",
segments: ["tools", "a+b[1]"],
}),
}),
);
expect(routeMocks.renderHousekeepingRoute).toHaveBeenCalledWith({
context,
translate: expect.any(Function),
match: {
routeId: "people.literal-tool",
domain: "people",
params: {},
canonicalHref: "/ase/people/tools/a%2Bb%5B1%5D",
},
});
});
it("returns 404 for an inaccessible matched route without invoking it", async () => {
routeMocks.getHousekeepingCapabilityContext.mockResolvedValue(
capabilityContext([PERMS.MOD_CFH_VIEW]),
);
it("rejects an unknown domain before translating", async () => {
await expect(
HousekeepingDomainPage({
params: Promise.resolve({ domain: "people", segments: ["bans"] }),
}),
).rejects.toThrow("NEXT_NOT_FOUND");
expect(routeMocks.getHousekeepingCapabilityContext).toHaveBeenCalledTimes(
1,
);
expect(routeMocks.renderHousekeepingRoute).not.toHaveBeenCalled();
});
it("rejects an unknown route before loading capability context", async () => {
await expect(
HousekeepingDomainPage({
params: Promise.resolve({ domain: "people", segments: ["unknown"] }),
AdminNextDomainPage({
params: Promise.resolve({ domain: "unknown" }),
}),
).rejects.toThrow("NEXT_NOT_FOUND");
expect(routeMocks.getHousekeepingCapabilityContext).not.toHaveBeenCalled();
expect(routeMocks.renderHousekeepingRoute).not.toHaveBeenCalled();
expect(routeMocks.getTranslations).not.toHaveBeenCalled();
});
});
@@ -869,247 +561,247 @@ describe("preview route import boundary", () => {
it.each([
[
"relative database import with resolver extension",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import db from "../../lib/db.js";',
"src/lib/db",
],
[
"aliased database import with resolver extension",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import db from "@/lib/db.js";',
"src/lib/db",
],
[
"action root import with resolver extension",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import actions from "../../actions.mjs";',
"src/actions",
],
[
"legacy mod root import with resolver extension",
"src/app/ase/layout.tsx",
"src/app/admin-next/layout.tsx",
'import mod from "../mod.cjs";',
"src/app/mod",
],
[
"database import with query suffix",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import db from "../../lib/db?server-only";',
"src/lib/db",
],
[
"action import with hash suffix",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import("../../actions/users#server")',
"src/actions/users",
],
[
"Windows-style relative database import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
String.raw`import db from "..\\..\\lib\\db";`,
"src/lib/db",
],
[
"Windows-style aliased database import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
String.raw`import db from "@\\lib\\db";`,
"src/lib/db",
],
[
"percent-encoded database import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import db from "../../lib/%64%62";',
"src/lib/db",
],
[
"optional CommonJS database require",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'require?.("../../lib/db")',
"src/lib/db",
],
[
"module database require",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'module.require("../../lib/db")',
"src/lib/db",
],
[
"require.resolve database access",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'require.resolve("../../lib/db")',
"src/lib/db",
],
[
"optional module database require",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'module.require?.("../../lib/db")',
"src/lib/db",
],
[
"optional require.resolve database access",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'require.resolve?.("../../lib/db")',
"src/lib/db",
],
[
"TypeScript import-equals database access",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import db = require("../../lib/db");',
"src/lib/db",
],
[
"U+2028 line-continuation database import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import db from "../\\' + "\u2028" + '../lib/db";',
"src/lib/db",
],
[
"U+2029 line-continuation database import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import db from "../\\' + "\u2029" + '../lib/db";',
"src/lib/db",
],
[
"parenthesized dynamic action import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import(("../../actions/users"))',
"src/actions/users",
],
[
"regex-brace template-expression action import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
`const x = \`${interpolationOpen}/}/.test(value) ? import("../../actions/users") : null}\`;`,
"src/actions/users",
],
[
"CommonJS database require",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'require("../../lib/db")',
"src/lib/db",
],
[
"template-literal dynamic action import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
"import(`../../actions/users`)",
"src/actions/users",
],
[
"TypeScript-asserted dynamic action import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import(("../../actions/users" as string))',
"src/actions/users",
],
[
"template-expression dynamic action import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
`const x = \`${interpolationOpen}import("../../actions/users")}\`;`,
"src/actions/users",
],
[
"nested template-expression dynamic action import",
"src/app/ase/[domain]/[[...segments]]/page.tsx",
`const x = \`${interpolationOpen}ready ? \`${interpolationOpen}import("../../../../actions/nested")}\` : ""}\`;`,
"src/app/admin-next/[domain]/page.tsx",
`const x = \`${interpolationOpen}ready ? \`${interpolationOpen}import("../../../actions/nested")}\` : ""}\`;`,
"src/actions/nested",
],
[
"unicode escaped dynamic app-action import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import("\\u002e\\u002e/actions/users")',
"src/app/actions/users",
],
[
"code-point escaped dynamic app-action import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import("\\u{2e}\\u{2e}/actions/users")',
"src/app/actions/users",
],
[
"hex escaped export-from auth import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'export * from "\\x2e\\x2e/\\x2e\\x2e/lib/auth";',
"src/lib/auth",
],
[
"escaped-slash permissions import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import permissions from "..\\/..\\/lib\\/permissions";',
"src/lib/permissions",
],
[
"unknown escape database import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import db from "../../\\lib/db";',
"src/lib/db",
],
[
"line-continuation database import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import db from "../\\' + "\n" + '../lib/db";',
"src/lib/db",
],
[
"aliased database descendant import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import { query } from "@/lib/db/query";',
"src/lib/db/query",
],
[
"root relative database import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import { db } from "../../lib/db";',
"src/lib/db",
],
[
"domain relative auth side-effect import",
"src/app/ase/[domain]/layout.tsx",
"src/app/admin-next/[domain]/layout.tsx",
'import "../../../lib/auth";',
"src/lib/auth",
],
[
"domain relative permissions export",
"src/app/ase/[domain]/[[...segments]]/page.tsx",
'export { getAdminContext } from "../../../../lib/permissions";',
"src/app/admin-next/[domain]/page.tsx",
'export { getAdminContext } from "../../../lib/permissions";',
"src/lib/permissions",
],
[
"root relative action dynamic import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import("../../actions/users")',
"src/actions/users",
],
[
"root relative app action export",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'export * from "../actions";',
"src/app/actions",
],
[
"domain relative legacy admin import",
"src/app/ase/[domain]/layout.tsx",
"src/app/admin-next/[domain]/layout.tsx",
'import page from "../../admin/users/page";',
"src/app/admin/users/page",
],
[
"root relative legacy mod dynamic import",
"src/app/ase/layout.tsx",
"src/app/admin-next/layout.tsx",
'import("../mod/users/page")',
"src/app/mod/users/page",
],
[
"Prisma TypeScript import type",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'type PrismaClient = import("@prisma/client").PrismaClient;',
"@prisma/client",
],
[
"Drizzle package import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import { sql } from "drizzle-orm";',
"drizzle-orm",
],
[
"mysql2 package import",
"src/app/ase/page.tsx",
"src/app/admin-next/page.tsx",
'import type { Pool } from "mysql2";',
"mysql2",
],
@@ -1142,7 +834,7 @@ describe("preview route import boundary", () => {
],
] as const)("fails closed for non-literal %s", (_name, source, violation) => {
expect(
findRouteImportBoundaryViolations(source, "src/app/ase/page.tsx"),
findRouteImportBoundaryViolations(source, "src/app/admin-next/page.tsx"),
).toContain(violation);
});
@@ -1151,7 +843,7 @@ describe("preview route import boundary", () => {
'import database from "@/lib/database.js?raw";',
'import dbTools from "../../lib/db-tools.ts";',
'import auth from "../../lib/authentication";',
'import preview from "../ase-shared";',
'import preview from "../admin-next-shared";',
'import prismaTools from "@prisma/client-tools";',
'import drizzleTools from "drizzle-orm-kit";',
'import mysqlTools from "mysql2-wrapper";',
@@ -1161,7 +853,7 @@ describe("preview route import boundary", () => {
].join("\n");
expect(
findRouteImportBoundaryViolations(source, "src/app/ase/page.tsx"),
findRouteImportBoundaryViolations(source, "src/app/admin-next/page.tsx"),
).toEqual([]);
});
});
@@ -1,189 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import {
anyCapability,
type HousekeepingCapabilityContext,
ok,
} from "../contracts";
import { orchestrateProviders, type ProviderPolicy } from "./orchestrate";
import type { HousekeepingProvider } from "./run-provider";
interface Item {
key: string;
label: string;
order: number;
}
const policy: ProviderPolicy<Item> = {
timeoutMs: 2_000,
perProviderLimit: 2,
combinedLimit: 3,
sort: (items) => [...items].sort((left, right) => left.order - right.order),
dedupeKey: (item) => item.key,
};
const allowedContext: HousekeepingCapabilityContext = {
actor: { id: 42, username: "operator", rank: 0 },
isSuperAdmin: false,
has: (slug) => slug === "housekeeping.read",
hasAny: (...slugs) => slugs.includes("housekeeping.read"),
hasAll: (...slugs) => slugs.every((slug) => slug === "housekeeping.read"),
};
function provider(
id: string,
items: readonly Item[],
): HousekeepingProvider<Item> {
return {
id,
capability: anyCapability("housekeeping.read"),
run: async () => ok(items, `${id}-correlation`),
};
}
describe("orchestrateProviders", () => {
it("preserves a successful sibling when another provider times out", async () => {
vi.useFakeTimers();
const slow: HousekeepingProvider<Item> = {
id: "slow",
capability: anyCapability("housekeeping.read"),
run: async (_context, signal) =>
await new Promise((_, reject) => {
signal.addEventListener("abort", () => reject(new Error("aborted")));
}),
};
const result = orchestrateProviders(
[slow, provider("fast", [{ key: "fast", label: "Fast", order: 1 }])],
allowedContext,
policy,
);
await vi.advanceTimersByTimeAsync(2_000);
expect(await result).toMatchObject({
items: [{ key: "fast", label: "Fast", order: 1 }],
errors: [{ providerId: "slow", code: "TIMEOUT" }],
});
vi.useRealTimers();
});
it("preserves a successful sibling when another provider throws synchronously", async () => {
const synchronousFailure: HousekeepingProvider<Item> = {
id: "synchronously-broken",
capability: anyCapability("housekeeping.read"),
run: () => {
throw new Error("synchronous dependency failure");
},
};
expect(
await orchestrateProviders(
[
synchronousFailure,
provider("fast", [{ key: "fast", label: "Fast", order: 1 }]),
],
allowedContext,
policy,
),
).toMatchObject({
items: [{ key: "fast", label: "Fast", order: 1 }],
errors: [{ providerId: "synchronously-broken", code: "INTERNAL" }],
});
});
it("keeps the timeout result and sibling success when a provider rejects late", async () => {
vi.useFakeTimers();
let observedAbort = false;
const lateFailure: HousekeepingProvider<Item> = {
id: "late-failure",
capability: anyCapability("housekeeping.read"),
run: async (_context, signal) =>
await new Promise<never>((_resolve, reject) => {
signal.addEventListener("abort", () => {
observedAbort = true;
});
setTimeout(() => reject(new Error("late dependency failure")), 2_001);
}),
};
const batch = orchestrateProviders(
[
lateFailure,
provider("fast", [{ key: "fast", label: "Fast", order: 1 }]),
],
allowedContext,
policy,
);
await vi.advanceTimersByTimeAsync(2_000);
expect(await batch).toMatchObject({
items: [{ key: "fast", label: "Fast", order: 1 }],
errors: [{ providerId: "late-failure", code: "TIMEOUT" }],
});
expect(observedAbort).toBe(true);
await vi.advanceTimersByTimeAsync(1);
expect(await batch).toMatchObject({
items: [{ key: "fast", label: "Fast", order: 1 }],
errors: [{ providerId: "late-failure", code: "TIMEOUT" }],
});
vi.useRealTimers();
});
it("keeps the first duplicate before applying deterministic sorting", async () => {
const result = await orchestrateProviders(
[
provider("first", [
{ key: "shared", label: "First", order: 2 },
{ key: "unique", label: "Unique", order: 1 },
]),
provider("second", [{ key: "shared", label: "Second", order: 0 }]),
],
allowedContext,
policy,
);
expect(result.items).toEqual([
{ key: "unique", label: "Unique", order: 1 },
{ key: "shared", label: "First", order: 2 },
]);
});
it("enforces per-provider and combined caps before sorting", async () => {
const result = await orchestrateProviders(
[
provider("first", [
{ key: "one", label: "One", order: 3 },
{ key: "two", label: "Two", order: 2 },
{ key: "discarded", label: "Discarded", order: 0 },
]),
provider("second", [
{ key: "three", label: "Three", order: 1 },
{ key: "combined-discarded", label: "Combined discarded", order: 0 },
]),
],
allowedContext,
policy,
);
expect(result.items).toEqual([
{ key: "three", label: "Three", order: 1 },
{ key: "two", label: "Two", order: 2 },
{ key: "one", label: "One", order: 3 },
]);
});
it("applies the per-provider cap before cross-provider deduplication", async () => {
const result = await orchestrateProviders(
[
provider("first", [{ key: "shared", label: "First", order: 1 }]),
provider("second", [
{ key: "shared", label: "Second", order: 2 },
{ key: "unique", label: "Unique", order: 3 },
]),
],
allowedContext,
{ ...policy, perProviderLimit: 1, combinedLimit: 5 },
);
expect(result.items).toEqual([{ key: "shared", label: "First", order: 1 }]);
});
});
@@ -1,55 +0,0 @@
import type {
HousekeepingCapabilityContext,
HousekeepingErrorCode,
} from "../contracts";
import { createCorrelationId } from "../correlation";
import { type HousekeepingProvider, runProvider } from "./run-provider";
export interface ProviderPolicy<T> {
timeoutMs: number;
perProviderLimit: number;
combinedLimit: number;
sort(items: readonly T[]): readonly T[];
dedupeKey(item: T): string;
}
export interface ProviderBatchResult<T> {
items: readonly T[];
errors: readonly { providerId: string; code: HousekeepingErrorCode }[];
correlationId: string;
}
export async function orchestrateProviders<T>(
providers: readonly HousekeepingProvider<T>[],
context: HousekeepingCapabilityContext,
policy: ProviderPolicy<T>,
): Promise<ProviderBatchResult<T>> {
const providerResults = await Promise.all(
providers.map((provider) =>
runProvider(provider, context, policy.timeoutMs),
),
);
const items: T[] = [];
const seen = new Set<string>();
const errors: { providerId: string; code: HousekeepingErrorCode }[] = [];
for (const result of providerResults) {
if (result.error !== undefined) {
errors.push({ providerId: result.providerId, code: result.error });
}
for (const item of result.items.slice(0, policy.perProviderLimit)) {
if (items.length >= policy.combinedLimit) break;
const key = policy.dedupeKey(item);
if (seen.has(key)) continue;
seen.add(key);
items.push(item);
}
}
return {
items: policy.sort(items),
errors,
correlationId: createCorrelationId(),
};
}
@@ -1,150 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import {
anyCapability,
fail,
type HousekeepingCapabilityContext,
ok,
} from "../contracts";
import { type HousekeepingProvider, runProvider } from "./run-provider";
function context(granted: readonly string[]): HousekeepingCapabilityContext {
return {
actor: { id: 42, username: "operator", rank: 999 },
isSuperAdmin: false,
has: (slug) => granted.includes(slug),
hasAny: (...slugs) => slugs.some((slug) => granted.includes(slug)),
hasAll: (...slugs) => slugs.every((slug) => granted.includes(slug)),
};
}
describe("runProvider", () => {
it("aborts a slow provider at its 2,000 ms deadline and returns TIMEOUT", async () => {
vi.useFakeTimers();
let observedAbort = false;
const provider: HousekeepingProvider<string> = {
id: "slow",
capability: anyCapability("housekeeping.read"),
run: async (_context, signal) =>
await new Promise((_, reject) => {
signal.addEventListener("abort", () => {
observedAbort = true;
reject(new Error("aborted"));
});
}),
};
const result = runProvider(provider, context(["housekeeping.read"]), 2_000);
await vi.advanceTimersByTimeAsync(2_000);
expect(await result).toEqual({
providerId: "slow",
items: [],
error: "TIMEOUT",
});
expect(observedAbort).toBe(true);
vi.useRealTimers();
});
it("skips a provider whose required capability is absent", async () => {
let invoked = false;
const provider: HousekeepingProvider<string> = {
id: "restricted",
capability: anyCapability("housekeeping.read"),
run: async () => {
invoked = true;
return ok(["private"], "provider-correlation");
},
};
expect(await runProvider(provider, context([]), 2_000)).toEqual({
providerId: "restricted",
items: [],
});
expect(invoked).toBe(false);
});
it("maps one thrown provider failure to one INTERNAL partial error", async () => {
const provider: HousekeepingProvider<string> = {
id: "broken",
capability: anyCapability("housekeeping.read"),
run: async () => {
throw new Error("database unavailable");
},
};
expect(
await runProvider(provider, context(["housekeeping.read"]), 2_000),
).toEqual({ providerId: "broken", items: [], error: "INTERNAL" });
});
it("maps a synchronous provider throw to one INTERNAL partial error", async () => {
const provider: HousekeepingProvider<string> = {
id: "synchronously-broken",
capability: anyCapability("housekeeping.read"),
run: () => {
throw new Error("synchronous dependency failure");
},
};
await expect(
runProvider(provider, context(["housekeeping.read"]), 2_000),
).resolves.toEqual({
providerId: "synchronously-broken",
items: [],
error: "INTERNAL",
});
});
it("times out an abort-ignoring provider exactly at 2,000 ms", async () => {
vi.useFakeTimers();
let observedAbort = false;
const provider: HousekeepingProvider<string> = {
id: "abort-ignoring",
capability: anyCapability("housekeeping.read"),
run: async (_context, signal) => {
signal.addEventListener("abort", () => {
observedAbort = true;
});
return await new Promise<never>(() => undefined);
},
};
const result = runProvider(provider, context(["housekeeping.read"]), 2_000);
let settled = false;
void result.then(() => {
settled = true;
});
await vi.advanceTimersByTimeAsync(1_999);
expect(settled).toBe(false);
await vi.advanceTimersByTimeAsync(1);
expect(await result).toEqual({
providerId: "abort-ignoring",
items: [],
error: "TIMEOUT",
});
expect(observedAbort).toBe(true);
vi.useRealTimers();
});
it("preserves a returned typed provider failure code once", async () => {
const provider: HousekeepingProvider<string> = {
id: "dependency-failure",
capability: anyCapability("housekeeping.read"),
run: async () =>
fail(
"DEPENDENCY_UNAVAILABLE",
"errors.housekeeping.dependencyUnavailable",
"provider-correlation",
),
};
expect(
await runProvider(provider, context(["housekeeping.read"]), 2_000),
).toEqual({
providerId: "dependency-failure",
items: [],
error: "DEPENDENCY_UNAVAILABLE",
});
});
});
@@ -1,62 +0,0 @@
import { satisfiesCapability } from "../capability-context";
import type {
CapabilityRequirement,
HousekeepingCapabilityContext,
HousekeepingErrorCode,
HousekeepingResult,
} from "../contracts";
export interface HousekeepingProvider<T> {
id: string;
capability: CapabilityRequirement;
run(
context: HousekeepingCapabilityContext,
signal: AbortSignal,
): Promise<HousekeepingResult<readonly T[]>>;
}
export interface ProviderRunResult<T> {
providerId: string;
items: readonly T[];
error?: HousekeepingErrorCode;
}
export async function runProvider<T>(
provider: HousekeepingProvider<T>,
context: HousekeepingCapabilityContext,
timeoutMs: number,
): Promise<ProviderRunResult<T>> {
if (!satisfiesCapability(context, provider.capability)) {
return { providerId: provider.id, items: [] };
}
const controller = new AbortController();
let timeout: ReturnType<typeof setTimeout> | undefined;
const run = Promise.resolve()
.then(() => provider.run(context, controller.signal))
.then(
(result): ProviderRunResult<T> =>
result.ok
? { providerId: provider.id, items: result.data }
: { providerId: provider.id, items: [], error: result.error.code },
)
.catch(
(): ProviderRunResult<T> => ({
providerId: provider.id,
items: [],
error: "INTERNAL",
}),
);
const timeoutResult = new Promise<ProviderRunResult<T>>((resolve) => {
timeout = setTimeout(() => {
controller.abort();
resolve({ providerId: provider.id, items: [], error: "TIMEOUT" });
}, timeoutMs);
});
try {
return await Promise.race([run, timeoutResult]);
} finally {
if (timeout !== undefined) clearTimeout(timeout);
}
}
@@ -1,167 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import {
anyCapability,
type HousekeepingCapabilityContext,
} from "../contracts";
import type { HousekeepingRegistry } from "../registry";
import {
createHousekeepingRecentWorkService,
RECENT_WORK_POLICY,
} from "./recent-work";
const usersView = anyCapability(PERMS.USERS_VIEW);
const bansView = anyCapability(PERMS.BANS_VIEW);
function registry(routeCount = 2): HousekeepingRegistry {
return {
domains: [
{
id: "people",
labelKey: "people",
descriptionKey: "people.description",
iconId: "users",
canonicalHref: "/ase/people",
capability: usersView,
routes: Array.from({ length: routeCount }, (_, index) => ({
id: index === 0 ? "people.users" : `people.route-${index}`,
labelKey:
index === 0 ? "routes.people.users" : `routes.people.${index}`,
href:
index === 0
? "/ase/people/users"
: (`/ase/people/route-${index}` as const),
capability: index === 1 ? bansView : usersView,
})),
searchProviders: [],
inboxSources: [],
widgets: [],
},
],
};
}
const context: HousekeepingCapabilityContext = {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => slug === PERMS.USERS_VIEW,
hasAny: (...slugs) => slugs.includes(PERMS.USERS_VIEW),
hasAll: (...slugs) => slugs.every((slug) => slug === PERMS.USERS_VIEW),
};
describe("housekeeping recent work", () => {
it("derives newest authorized route visits and mutation audits, then deduplicates by route", async () => {
const loadRows = vi.fn().mockResolvedValue([
{
action: "housekeeping.route.visit",
target: "people.users",
domain: "people",
outcome: "success",
createdAt: "2026-08-30T10:01:00.000Z",
},
{
action: "people.user.update",
target: "People",
domain: "people",
outcome: "success",
createdAt: "2026-08-30T10:03:00.000Z",
},
{
action: "housekeeping.route.visit",
target: "people.route-1",
domain: "people",
outcome: "success",
createdAt: "2026-08-30T10:04:00.000Z",
},
{
action: "people.user.delete",
target: "People",
domain: "people",
outcome: "failure",
createdAt: "2026-08-30T10:05:00.000Z",
},
{
action: "people.unknown.update",
target: "People",
domain: "people",
outcome: "success",
createdAt: "2026-08-30T10:06:00.000Z",
},
]);
const service = createHousekeepingRecentWorkService({
registry: registry(),
loadRows,
writeAudit: vi.fn(),
createCorrelationId: () => "recent-1",
});
const result = await service.load(context);
expect(loadRows).toHaveBeenCalledWith(42, RECENT_WORK_POLICY.scanLimit);
expect(result).toEqual({
ok: true,
data: [
{
routeId: "people.users",
canonicalHref: "/ase/people/users",
labelKey: "routes.people.users",
occurredAt: "2026-08-30T10:03:00.000Z",
source: "audit",
},
],
correlationId: "recent-1",
});
});
it("caps authorized unique recent routes at twelve", async () => {
const rows = Array.from({ length: 14 }, (_, index) => ({
action: "housekeeping.route.visit",
target: index === 0 ? "people.users" : `people.route-${index + 1}`,
domain: "people",
outcome: "success",
createdAt: new Date(Date.UTC(2026, 7, 30, 12, index)).toISOString(),
}));
const service = createHousekeepingRecentWorkService({
registry: registry(16),
loadRows: vi.fn().mockResolvedValue(rows),
writeAudit: vi.fn(),
});
const result = await service.load(context);
expect(result.ok && result.data).toHaveLength(RECENT_WORK_POLICY.limit);
expect(result.ok && result.data[0]?.occurredAt).toBe(
"2026-08-30T12:13:00.000Z",
);
});
it("records only an authorized canonical route visit for the server actor", async () => {
const writeAudit = vi.fn().mockResolvedValue(undefined);
const service = createHousekeepingRecentWorkService({
registry: registry(),
loadRows: vi.fn(),
writeAudit,
createCorrelationId: () => "visit-1",
});
const allowed = await service.recordVisit("people.users", context);
const denied = await service.recordVisit("people.route-1", context);
expect(allowed).toMatchObject({
ok: true,
data: { routeId: "people.users", canonicalHref: "/ase/people/users" },
});
expect(writeAudit).toHaveBeenCalledOnce();
expect(writeAudit).toHaveBeenCalledWith(
expect.objectContaining({
userId: 42,
action: "housekeeping.route.visit",
target: "people.users",
domain: "people",
outcome: "success",
correlationId: "visit-1",
}),
);
expect(denied).toMatchObject({ ok: false, error: { code: "FORBIDDEN" } });
});
});
@@ -1,256 +0,0 @@
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 HousekeepingRecentAuditRow[]>;
readonly writeAudit: (entry: AuditEntry) => Promise<void>;
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<string>();
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<HousekeepingResult<readonly HousekeepingRecentItem[]>> {
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<HousekeepingResult<HousekeepingRecentItem>> {
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,
);
}
},
};
}
@@ -1,29 +1,5 @@
import { describe, expect, it } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import { CONTENT_INBOX_SOURCES } from "../domains/content/inbox";
import { CONTENT_ROUTES } from "../domains/content/routes";
import { CONTENT_SEARCH_PROVIDERS } from "../domains/content/search";
import { CONTENT_WIDGETS } from "../domains/content/widgets";
import { ECONOMY_INBOX_SOURCES } from "../domains/economy/inbox";
import { ECONOMY_ROUTES } from "../domains/economy/routes";
import { ECONOMY_SEARCH_PROVIDERS } from "../domains/economy/search";
import { ECONOMY_WIDGETS } from "../domains/economy/widgets";
import { HOTEL_INBOX_SOURCES } from "../domains/hotel/inbox";
import { HOTEL_ROUTES } from "../domains/hotel/routes";
import { HOTEL_SEARCH_PROVIDERS } from "../domains/hotel/search";
import { HOTEL_WIDGETS } from "../domains/hotel/widgets";
import { OPERATIONS_INBOX_SOURCES } from "../domains/operations/inbox";
import { OPERATIONS_ROUTES } from "../domains/operations/routes";
import { OPERATIONS_SEARCH_PROVIDERS } from "../domains/operations/search";
import { OPERATIONS_WIDGETS } from "../domains/operations/widgets";
import { PEOPLE_INBOX_SOURCES } from "../domains/people/inbox";
import { PEOPLE_ROUTES } from "../domains/people/routes";
import { PEOPLE_SEARCH_PROVIDERS } from "../domains/people/search";
import { PEOPLE_WIDGETS } from "../domains/people/widgets";
import { SYSTEM_INBOX_SOURCES } from "../domains/system/inbox";
import { SYSTEM_ROUTES } from "../domains/system/routes";
import { SYSTEM_SEARCH_PROVIDERS } from "../domains/system/search";
import { SYSTEM_WIDGETS } from "../domains/system/widgets";
import { HOUSEKEEPING_MANIFESTS } from "../manifests";
import { HOUSEKEEPING_MIGRATION_MATRIX } from "../migration/matrix";
import {
@@ -32,21 +8,14 @@ import {
} from "../migration/types";
import {
anyCapability,
type CanonicalHousekeepingHref,
type CapabilityRequirement,
type HousekeepingCapabilityContext,
type HousekeepingDomainManifest,
type HousekeepingInboxSource,
type HousekeepingSearchProvider,
type HousekeepingWidgetDefinition,
ok,
} from "./contracts";
import { defaultHousekeepingPreferences } from "./preferences/schema";
import {
createHousekeepingRegistry,
loadHousekeepingWidgets,
WIDGET_POLICY,
} from "./registry";
import { createHousekeepingRegistry } from "./registry";
const manifest = (
id: HousekeepingDomainId,
@@ -56,7 +25,7 @@ const manifest = (
labelKey: `pages.housekeeping.domains.${id}.title`,
descriptionKey: `pages.housekeeping.domains.${id}.description`,
iconId: "settings",
canonicalHref: id === "operations" ? "/ase" : `/ase/${id}`,
previewHref: `/admin-next/${id}`,
capability: anyCapability(capabilitySlug),
routes: [],
searchProviders: [],
@@ -104,7 +73,7 @@ const expectedManifests = [
{
id: "operations",
iconId: "inbox",
canonicalHref: "/ase",
previewHref: "/admin-next/operations",
labelKey: "pages.housekeeping.domains.operations.title",
descriptionKey: "pages.housekeeping.domains.operations.description",
slugs: [PERMS.ADMIN_DASHBOARD],
@@ -112,7 +81,7 @@ const expectedManifests = [
{
id: "people",
iconId: "users",
canonicalHref: "/ase/people",
previewHref: "/admin-next/people",
labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description",
slugs: [
@@ -143,7 +112,7 @@ const expectedManifests = [
{
id: "content",
iconId: "file-text",
canonicalHref: "/ase/content",
previewHref: "/admin-next/content",
labelKey: "pages.housekeeping.domains.content.title",
descriptionKey: "pages.housekeeping.domains.content.description",
slugs: [
@@ -166,7 +135,7 @@ const expectedManifests = [
{
id: "economy",
iconId: "gem",
canonicalHref: "/ase/economy",
previewHref: "/admin-next/economy",
labelKey: "pages.housekeeping.domains.economy.title",
descriptionKey: "pages.housekeeping.domains.economy.description",
slugs: [
@@ -179,7 +148,7 @@ const expectedManifests = [
{
id: "hotel",
iconId: "hotel",
canonicalHref: "/ase/hotel",
previewHref: "/admin-next/hotel",
labelKey: "pages.housekeeping.domains.hotel.title",
descriptionKey: "pages.housekeeping.domains.hotel.description",
slugs: [
@@ -196,7 +165,7 @@ const expectedManifests = [
{
id: "system",
iconId: "settings",
canonicalHref: "/ase/system",
previewHref: "/admin-next/system",
labelKey: "pages.housekeeping.domains.system.title",
descriptionKey: "pages.housekeeping.domains.system.description",
slugs: [
@@ -239,9 +208,9 @@ describe("housekeeping registry", () => {
it("rejects invalid preview and empty translation keys", () => {
expect(() =>
createHousekeepingRegistry([
{ ...manifest("people"), canonicalHref: "/ase" },
{ ...manifest("people"), previewHref: "/admin-next/operations" },
]),
).toThrow("invalid canonical href");
).toThrow("invalid preview href");
expect(() =>
createHousekeepingRegistry([{ ...manifest("people"), labelKey: " " }]),
).toThrow("empty label key");
@@ -263,13 +232,13 @@ describe("housekeeping registry", () => {
{
id: "users",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/users" as CanonicalHousekeepingHref,
href: "/admin-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW),
},
{
id: "users",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/staff",
href: "/admin-next/people/staff",
capability: anyCapability(PERMS.USERS_VIEW),
},
],
@@ -284,13 +253,13 @@ describe("housekeeping registry", () => {
{
id: "users",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/users" as CanonicalHousekeepingHref,
href: "/admin-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW),
},
{
id: "staff",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/users" as CanonicalHousekeepingHref,
href: "/admin-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW),
},
],
@@ -299,37 +268,11 @@ describe("housekeeping registry", () => {
).toThrow("duplicate route href");
});
it("rejects duplicate dynamic route shapes regardless of parameter name", () => {
const base = manifest("people");
expect(() =>
createHousekeepingRegistry([
{
...base,
routes: [
{
id: "people.user-detail",
labelKey: "pages.housekeeping.routes.people.user-detail",
href: "/ase/people/users/:id",
capability: anyCapability(PERMS.USERS_VIEW),
},
{
id: "people.user-profile",
labelKey: "pages.housekeeping.routes.people.user-profile",
href: "/ase/people/users/:username",
capability: anyCapability(PERMS.USERS_VIEW),
},
],
},
]),
).toThrow("duplicate dynamic route shape");
});
it("rejects empty route fields and unknown capability slugs", () => {
const route = {
id: "users",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/users" as CanonicalHousekeepingHref,
href: "/admin-next/people/users",
capability: anyCapability(PERMS.USERS_VIEW),
};
@@ -340,10 +283,7 @@ describe("housekeeping registry", () => {
).toThrow("empty route id");
expect(() =>
createHousekeepingRegistry([
{
...manifest("people"),
routes: [{ ...route, href: " " as CanonicalHousekeepingHref }],
},
{ ...manifest("people"), routes: [{ ...route, href: " " }] },
]),
).toThrow("empty route href");
expect(() =>
@@ -379,70 +319,14 @@ describe("housekeeping registry", () => {
expect(actual).toMatchObject({
id: expected.id,
iconId: expected.iconId,
canonicalHref: expected.canonicalHref,
previewHref: expected.previewHref,
labelKey: expected.labelKey,
descriptionKey: expected.descriptionKey,
});
expect(actual.routes).toEqual(
expected.id === "operations"
? OPERATIONS_ROUTES
: expected.id === "people"
? PEOPLE_ROUTES
: expected.id === "content"
? CONTENT_ROUTES
: expected.id === "economy"
? ECONOMY_ROUTES
: expected.id === "hotel"
? HOTEL_ROUTES
: expected.id === "system"
? SYSTEM_ROUTES
: [],
);
expect(actual.searchProviders).toEqual(
expected.id === "operations"
? OPERATIONS_SEARCH_PROVIDERS
: expected.id === "people"
? PEOPLE_SEARCH_PROVIDERS
: expected.id === "content"
? CONTENT_SEARCH_PROVIDERS
: expected.id === "economy"
? ECONOMY_SEARCH_PROVIDERS
: expected.id === "hotel"
? HOTEL_SEARCH_PROVIDERS
: expected.id === "system"
? SYSTEM_SEARCH_PROVIDERS
: [],
);
expect(actual.inboxSources).toEqual(
expected.id === "operations"
? OPERATIONS_INBOX_SOURCES
: expected.id === "people"
? PEOPLE_INBOX_SOURCES
: expected.id === "content"
? CONTENT_INBOX_SOURCES
: expected.id === "economy"
? ECONOMY_INBOX_SOURCES
: expected.id === "hotel"
? HOTEL_INBOX_SOURCES
: expected.id === "system"
? SYSTEM_INBOX_SOURCES
: [],
);
expect(actual.widgets).toEqual(
expected.id === "operations"
? OPERATIONS_WIDGETS
: expected.id === "people"
? PEOPLE_WIDGETS
: expected.id === "content"
? CONTENT_WIDGETS
: expected.id === "economy"
? ECONOMY_WIDGETS
: expected.id === "hotel"
? HOTEL_WIDGETS
: expected.id === "system"
? SYSTEM_WIDGETS
: [],
);
expect(actual.routes).toEqual([]);
expect(actual.searchProviders).toEqual([]);
expect(actual.inboxSources).toEqual([]);
expect(actual.widgets).toEqual([]);
expect(actual.capability).toEqual({ mode: "any", slugs: expected.slugs });
}
});
@@ -467,7 +351,7 @@ describe("housekeeping registry", () => {
}
});
it("rejects duplicates within each provider kind across domain manifests", () => {
it("rejects duplicate provider and widget ids across domain manifests", () => {
expect(() =>
createHousekeepingRegistry([
{ ...manifest("people"), searchProviders: [searchProvider("shared")] },
@@ -503,7 +387,7 @@ describe("housekeeping registry", () => {
widgets: [widget("shared", "content")],
},
]),
).not.toThrow();
).toThrow("duplicate widget id: shared");
});
it("rejects provider and widget ownership outside their manifest", () => {
@@ -583,88 +467,6 @@ describe("housekeeping registry", () => {
).toThrow("invalid widget kind: people.queue");
});
it("loads only authorized mandatory and enabled optional widgets with isolated timeouts", async () => {
const context: HousekeepingCapabilityContext = {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => slug === PERMS.USERS_VIEW,
hasAny: (...slugs) => slugs.includes(PERMS.USERS_VIEW),
hasAll: (...slugs) => slugs.every((slug) => slug === PERMS.USERS_VIEW),
};
let deniedCalls = 0;
const base = {
...manifest("people"),
capability: anyCapability(PERMS.USERS_VIEW),
};
const registry = createHousekeepingRegistry([
{
...base,
widgets: [
{
...widget("people.required"),
kind: "mandatory",
load: async () => ok("required", "required"),
},
{
...widget("people.optional"),
load: async () => ok("optional", "optional"),
},
{
...widget(
"people.denied",
"people",
anyCapability(PERMS.BANS_VIEW),
),
load: async () => {
deniedCalls += 1;
return ok("denied", "denied");
},
},
{
...widget("people.slow"),
load: async (_context, signal) =>
new Promise((resolve) => {
signal.addEventListener("abort", () =>
resolve(ok("late", "late")),
);
}),
},
],
},
]);
const preferences = {
...defaultHousekeepingPreferences(),
widgetOrder: [
"stale.widget",
"people.optional",
"people.required",
"people.slow",
],
enabledOptionalWidgetIds: [
"people.optional",
"people.denied",
"people.slow",
],
};
const result = await loadHousekeepingWidgets(
registry,
preferences,
context,
5,
);
expect(WIDGET_POLICY.timeoutMs).toBe(2_000);
expect(result.widgets).toEqual([
{ id: "people.optional", data: "optional" },
{ id: "people.required", data: "required" },
]);
expect(result.errors).toEqual([
{ widgetId: "people.slow", code: "TIMEOUT" },
]);
expect(deniedCalls).toBe(0);
});
it("rejects empty domain and route capability requirements", () => {
expect(() =>
createHousekeepingRegistry([
@@ -679,7 +481,7 @@ describe("housekeeping registry", () => {
{
id: "users",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/people/users" as CanonicalHousekeepingHref,
href: "/admin-next/people/users",
capability: { mode: "any", slugs: [] },
},
],
@@ -692,7 +494,7 @@ describe("housekeeping registry", () => {
const route = {
id: "shared",
labelKey: "pages.housekeeping.domains.people.title",
href: "/ase/shared" as CanonicalHousekeepingHref,
href: "/admin-next/shared",
capability: anyCapability(PERMS.USERS_VIEW),
};
@@ -701,12 +503,7 @@ describe("housekeeping registry", () => {
{ ...manifest("people"), routes: [route] },
{
...manifest("content"),
routes: [
{
...route,
href: "/ase/content/shared" as CanonicalHousekeepingHref,
},
],
routes: [{ ...route, href: "/admin-next/content/shared" }],
},
]),
).toThrow("duplicate route id");
@@ -721,25 +518,3 @@ describe("housekeeping registry", () => {
).toThrow("duplicate route href");
});
});
it("accepts the canonical operations root and rejects legacy admin hrefs", () => {
const canonicalOperations = manifest("operations") as unknown as Record<
string,
unknown
>;
delete canonicalOperations.canonicalHref;
canonicalOperations.canonicalHref = "/ase";
expect(() =>
createHousekeepingRegistry([
canonicalOperations as unknown as HousekeepingDomainManifest,
]),
).not.toThrow();
canonicalOperations.canonicalHref = "/admin";
expect(() =>
createHousekeepingRegistry([
canonicalOperations as unknown as HousekeepingDomainManifest,
]),
).toThrow("invalid canonical href");
});
@@ -1,20 +1,9 @@
import { PERMS } from "@/lib/permission-slugs";
import { HOUSEKEEPING_DOMAIN_IDS } from "../migration/types";
import { satisfiesCapability } from "./capability-context";
import type {
CapabilityRequirement,
HousekeepingCapabilityContext,
HousekeepingDomainManifest,
HousekeepingErrorCode,
HousekeepingWidgetDefinition,
} from "./contracts";
import { ok } from "./contracts";
import { reconcilePreferences } from "./preferences/reconcile";
import type { HousekeepingPreferences } from "./preferences/schema";
import {
type HousekeepingProvider,
runProvider,
} from "./providers/run-provider";
const approvedDomainIds = new Set<string>(HOUSEKEEPING_DOMAIN_IDS);
const knownCapabilitySlugs = new Set<string>(Object.values(PERMS));
@@ -28,86 +17,13 @@ export interface HousekeepingRegistry {
domains: readonly HousekeepingDomainManifest[];
}
export const WIDGET_POLICY = Object.freeze({ timeoutMs: 2_000 } as const);
export interface HousekeepingWidgetLoadResult {
readonly widgets: readonly { readonly id: string; readonly data: unknown }[];
readonly errors: readonly {
readonly widgetId: string;
readonly code: HousekeepingErrorCode;
}[];
}
function widgetProvider(
widget: HousekeepingWidgetDefinition,
): HousekeepingProvider<unknown> {
return {
id: widget.id,
capability: widget.capability,
async run(context, signal) {
const result = await widget.load(context, signal);
return result.ok ? ok([result.data], result.correlationId) : result;
},
};
}
export async function loadHousekeepingWidgets(
registry: HousekeepingRegistry,
preferences: HousekeepingPreferences,
context: HousekeepingCapabilityContext,
timeoutMs: number = WIDGET_POLICY.timeoutMs,
): Promise<HousekeepingWidgetLoadResult> {
const reconciled = reconcilePreferences(preferences, registry, context);
const enabledOptionalIds = new Set(reconciled.enabledOptionalWidgetIds);
const selected = registry.domains
.filter((domain) => satisfiesCapability(context, domain.capability))
.flatMap((domain) => domain.widgets)
.filter(
(widget) =>
satisfiesCapability(context, widget.capability) &&
(widget.kind === "mandatory" || enabledOptionalIds.has(widget.id)),
);
const selectedById = new Map(selected.map((widget) => [widget.id, widget]));
const orderedIds = reconciled.widgetOrder.filter((id) =>
selectedById.has(id),
);
for (const widget of selected) {
if (!orderedIds.includes(widget.id)) orderedIds.push(widget.id);
}
const ordered = orderedIds.flatMap((id) => {
const widget = selectedById.get(id);
return widget ? [widget] : [];
});
const outcomes = await Promise.all(
ordered.map((widget) =>
runProvider(widgetProvider(widget), context, timeoutMs),
),
);
return {
widgets: outcomes.flatMap((outcome) =>
outcome.error === undefined
? [{ id: outcome.providerId, data: outcome.items[0] }]
: [],
),
errors: outcomes.flatMap((outcome) =>
outcome.error === undefined
? []
: [{ widgetId: outcome.providerId, code: outcome.error }],
),
};
}
export function createHousekeepingRegistry(
manifests: readonly HousekeepingDomainManifest[],
): HousekeepingRegistry {
const domainIds = new Set<string>();
const routeIds = new Set<string>();
const routeHrefs = new Set<string>();
const routeShapes = new Set<string>();
const searchProviderIds = new Set<string>();
const inboxSourceIds = new Set<string>();
const widgetIds = new Set<string>();
const registryEntryIds = new Set<string>();
for (const manifest of manifests) {
if (!approvedDomainIds.has(manifest.id)) {
@@ -118,11 +34,8 @@ export function createHousekeepingRegistry(
}
domainIds.add(manifest.id);
if (
manifest.canonicalHref !==
(manifest.id === "operations" ? "/ase" : `/ase/${manifest.id}`)
) {
throw new Error(`invalid canonical href: ${manifest.canonicalHref}`);
if (manifest.previewHref !== `/admin-next/${manifest.id}`) {
throw new Error(`invalid preview href: ${manifest.previewHref}`);
}
validateNonEmpty(manifest.labelKey, "label key");
validateNonEmpty(manifest.descriptionKey, "description key");
@@ -131,19 +44,19 @@ export function createHousekeepingRegistry(
manifest.searchProviders,
manifest.id,
"search provider",
searchProviderIds,
registryEntryIds,
);
validateOwnedRegistryEntries(
manifest.inboxSources,
manifest.id,
"inbox source",
inboxSourceIds,
registryEntryIds,
);
validateOwnedRegistryEntries(
manifest.widgets,
manifest.id,
"widget",
widgetIds,
registryEntryIds,
);
for (const widget of manifest.widgets) {
if (widget.kind !== "mandatory" && widget.kind !== "optional") {
@@ -159,20 +72,11 @@ export function createHousekeepingRegistry(
routeIds.add(route.id);
validateNonEmpty(route.href, "route href");
if (route.href !== "/ase" && !route.href.startsWith("/ase/")) {
throw new Error(`invalid canonical route href: ${route.href}`);
}
if (routeHrefs.has(route.href)) {
throw new Error(`duplicate route href: ${route.href}`);
}
routeHrefs.add(route.href);
const routeShape = dynamicRouteShape(route.href);
if (routeShape && routeShapes.has(routeShape)) {
throw new Error(`duplicate dynamic route shape: ${routeShape}`);
}
if (routeShape) routeShapes.add(routeShape);
validateNonEmpty(route.labelKey, "route label key");
validateCapability(route.capability);
}
@@ -181,20 +85,6 @@ export function createHousekeepingRegistry(
return { domains: Object.freeze([...manifests]) };
}
function dynamicRouteShape(href: string): string | null {
let hasDynamicSegment = false;
const shape = href
.split("/")
.map((segment) => {
if (!segment.startsWith(":")) return segment;
hasDynamicSegment = true;
return ":";
})
.join("/");
return hasDynamicSegment ? shape : null;
}
function validateOwnedRegistryEntries(
entries: readonly OwnedRegistryEntry[],
owner: HousekeepingDomainManifest["id"],
@@ -1,97 +0,0 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";
import { CommandDeck } from "./shell/command-deck";
import { HousekeepingShell } from "./shell/housekeeping-shell";
vi.mock("@/actions/housekeeping-search", () => ({
executeHousekeepingSearch: vi.fn(),
}));
const labels = {
skipToContent: "Skip",
primaryNavigation: "Primary",
contextualNavigation: "Context",
command: "Search",
preview: "Preview",
backToSite: "Back",
operatorRank: "Rank 6",
commandDeck: {
placeholder: "Search",
navigation: "Navigation",
commands: "Commands",
entities: "Entities",
loading: "Loading",
empty: "Empty",
partial: "Partial",
close: "Close",
},
};
describe("housekeeping responsive contract", () => {
it("stacks both navigation levels below lg without removing capabilities", () => {
const html = renderToStaticMarkup(
<HousekeepingShell
actor={{ id: 7, username: "Nora", rank: 6 }}
activeDomainId="operations"
domains={[
{
id: "operations",
href: "/ase/operations",
iconId: "inbox",
label: "Operations",
description: "Operations",
items: [{ id: "workspace", href: "/ase", label: "Workspace" }],
},
]}
labels={labels}
search={vi.fn()}
>
<div>Body</div>
</HousekeepingShell>,
);
expect(html).toContain('data-responsive-mode="stacked-below-lg"');
expect(html).toContain('data-navigation-collapse="domain"');
expect(html).toContain('data-navigation-collapse="context"');
expect(html).toContain('href="/ase/operations"');
expect(html).toContain('href="/ase"');
expect(html).toContain("overflow-x-auto");
expect(html).toContain("min-w-0");
});
it("keeps the command deck bounded at a 320px viewport", () => {
const source = readFileSync(
resolve(
process.cwd(),
"src/features/housekeeping/foundation/shell/command-deck.tsx",
),
"utf8",
);
const trigger = renderToStaticMarkup(
<CommandDeck
labels={{ ...labels.commandDeck, trigger: "Search" }}
search={vi.fn()}
/>,
);
expect(trigger).toContain("min-w-0");
expect(source).toContain("max-h-[calc(100dvh-2rem)]");
expect(source).toContain("overflow-hidden");
expect(source).toContain("p-2 sm:p-4");
});
it("provides non-overflow and reduced-motion safeguards for housekeeping content", () => {
const css = readFileSync(
resolve(process.cwd(), "src/app/globals.css"),
"utf8",
);
expect(css).toContain("[data-housekeeping-root]");
expect(css).toContain("#housekeeping-content table");
expect(css).toContain("overflow-x: auto");
expect(css).toContain("@media (prefers-reduced-motion: reduce)");
expect(css).toContain("transition-duration: 0.01ms");
});
});
@@ -1,20 +0,0 @@
import { describe, expect, it } from "vitest";
import type { CanonicalHousekeepingHref } from "../contracts";
import { HOUSEKEEPING_ROOT, toHousekeepingHref } from "./href";
describe("toHousekeepingHref", () => {
it("keeps the canonical operations root unchanged", () => {
expect(HOUSEKEEPING_ROOT).toBe("/ase");
expect(toHousekeepingHref("/ase")).toBe("/ase");
});
it("keeps canonical domain routes unchanged", () => {
expect(toHousekeepingHref("/ase/people/users")).toBe("/ase/people/users");
});
it("rejects paths outside the canonical ase namespace", () => {
expect(() =>
toHousekeepingHref("/admin" as CanonicalHousekeepingHref),
).toThrow("invalid canonical housekeeping href");
});
});
@@ -1,13 +0,0 @@
import type { CanonicalHousekeepingHref } from "../contracts";
export const HOUSEKEEPING_ROOT = "/ase" as const;
export function toHousekeepingHref(
href: CanonicalHousekeepingHref,
): CanonicalHousekeepingHref {
if (href !== HOUSEKEEPING_ROOT && !href.startsWith(`${HOUSEKEEPING_ROOT}/`)) {
throw new Error(`invalid canonical housekeeping href: ${href}`);
}
return href;
}
@@ -1,154 +0,0 @@
import { describe, expect, it } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import {
anyCapability,
type CanonicalHousekeepingHref,
type HousekeepingDomainManifest,
} from "../contracts";
import { createHousekeepingRegistry } from "../registry";
import { matchHousekeepingRoute } from "./match-route";
const requirement = anyCapability(PERMS.USERS_VIEW);
function peopleManifest(
routes: HousekeepingDomainManifest["routes"],
): HousekeepingDomainManifest {
return {
id: "people",
labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description",
iconId: "users",
canonicalHref: "/ase/people",
capability: requirement,
routes,
searchProviders: [],
inboxSources: [],
widgets: [],
};
}
function route(
id: string,
href: CanonicalHousekeepingHref,
): HousekeepingDomainManifest["routes"][number] {
return {
id,
labelKey: `pages.housekeeping.routes.${id}`,
href,
capability: requirement,
};
}
const registry = createHousekeepingRegistry([
peopleManifest([
route("people.user-detail", "/ase/people/users/:id"),
route("people.user-create", "/ase/people/users/new"),
route("people.users", "/ase/people/users"),
route("people.ticket-note", "/ase/people/tickets/:ticketId/notes/:noteId"),
route("people.literal-tool", "/ase/people/tools/a+b[1]"),
]),
]);
describe("matchHousekeepingRoute", () => {
it("matches a static route without parameters", () => {
expect(matchHousekeepingRoute(registry, "/ase/people/users")).toEqual({
routeId: "people.users",
domain: "people",
params: {},
canonicalHref: "/ase/people/users",
});
});
it("prefers a static route over a dynamic route with the same depth", () => {
expect(matchHousekeepingRoute(registry, "/ase/people/users/new")).toEqual({
routeId: "people.user-create",
domain: "people",
params: {},
canonicalHref: "/ase/people/users/new",
});
});
it("matches a dynamic segment and decodes its parameter", () => {
expect(
matchHousekeepingRoute(registry, "/ase/people/users/Ada%20Lovelace"),
).toEqual({
routeId: "people.user-detail",
domain: "people",
params: { id: "Ada Lovelace" },
canonicalHref: "/ase/people/users/Ada%20Lovelace",
});
});
it("matches nested dynamic segments independently", () => {
expect(
matchHousekeepingRoute(
registry,
"/ase/people/tickets/42/notes/follow-up",
),
).toEqual({
routeId: "people.ticket-note",
domain: "people",
params: { ticketId: "42", noteId: "follow-up" },
canonicalHref: "/ase/people/tickets/42/notes/follow-up",
});
});
it("treats regular-expression metacharacters as literal segment text", () => {
expect(
matchHousekeepingRoute(registry, "/ase/people/tools/a+b[1]"),
)?.toMatchObject({ routeId: "people.literal-tool" });
expect(
matchHousekeepingRoute(registry, "/ase/people/tools/ab1"),
).toBeNull();
});
it.each([
{
routes: [
route("people.kind-settings", "/ase/people/:kind/settings"),
route("people.user-detail", "/ase/people/users/:id"),
],
},
{
routes: [
route("people.user-detail", "/ase/people/users/:id"),
route("people.kind-settings", "/ase/people/:kind/settings"),
],
},
])(
"prefers the earliest static segment regardless of registration order",
({ routes }) => {
const orderedRegistry = createHousekeepingRegistry([
peopleManifest(routes),
]);
expect(
matchHousekeepingRoute(orderedRegistry, "/ase/people/users/settings"),
)?.toMatchObject({
routeId: "people.user-detail",
params: { id: "settings" },
});
},
);
it.each([
"/ase/people/unknown",
"/ase/economy/users",
"/ase/system/users/Ada",
])("returns null for unknown or cross-domain path %s", (path) => {
expect(matchHousekeepingRoute(registry, path)).toBeNull();
});
it.each([
"ase/people/users",
"/ase//people/users",
"/ase/people/users/",
"/ase/people/users?tab=active",
"/ase/people/users/%",
"/ase/people/users/%2Fsystem",
"/ase/people/users/..",
"/ase\\people\\users",
])("fails closed for malformed canonical path %s", (path) => {
expect(matchHousekeepingRoute(registry, path)).toBeNull();
});
});
@@ -1,176 +0,0 @@
import type { HousekeepingDomainId } from "../../migration/types";
import type { CanonicalHousekeepingHref } from "../contracts";
import type { HousekeepingRegistry } from "../registry";
export interface HousekeepingRouteMatch {
routeId: string;
domain: HousekeepingDomainId;
params: Readonly<Record<string, string>>;
canonicalHref: CanonicalHousekeepingHref;
}
interface ParsedCanonicalPath {
segments: readonly string[];
}
interface RoutePatternSegment {
literal: string | null;
parameterName: string | null;
}
interface RouteCandidate {
routeId: string;
domain: HousekeepingDomainId;
patternSegments: readonly RoutePatternSegment[];
}
export function matchHousekeepingRoute(
registry: HousekeepingRegistry,
canonicalPath: string,
): HousekeepingRouteMatch | null {
const path = parseCanonicalPath(canonicalPath);
if (!path) return null;
const candidates = registry.domains
.flatMap((domain) =>
domain.routes.flatMap((route) => {
if (!routeBelongsToDomain(route.href, domain.canonicalHref)) return [];
const patternSegments = parseRoutePattern(route.href);
return patternSegments
? [{ routeId: route.id, domain: domain.id, patternSegments }]
: [];
}),
)
.sort(compareSpecificity);
for (const candidate of candidates) {
const params = matchSegments(candidate, path);
if (!params) continue;
return {
routeId: candidate.routeId,
domain: candidate.domain,
params: Object.freeze(params),
canonicalHref: canonicalPath as CanonicalHousekeepingHref,
};
}
return null;
}
function parseCanonicalPath(canonicalPath: string): ParsedCanonicalPath | null {
if (
canonicalPath !== canonicalPath.trim() ||
!canonicalPath.startsWith("/ase") ||
canonicalPath.includes("?") ||
canonicalPath.includes("#") ||
canonicalPath.includes("\\") ||
canonicalPath.endsWith("/")
) {
return null;
}
const rawSegments = canonicalPath.slice(1).split("/");
if (rawSegments[0] !== "ase" || rawSegments.some((segment) => !segment)) {
return null;
}
const segments: string[] = [];
for (const segment of rawSegments) {
const decoded = decodeCanonicalSegment(segment);
if (decoded === null) return null;
segments.push(decoded);
}
return { segments };
}
function parseRoutePattern(
routeHref: CanonicalHousekeepingHref,
): readonly RoutePatternSegment[] | null {
const segments: RoutePatternSegment[] = [];
for (const segment of routeHref.slice(1).split("/")) {
if (segment.startsWith(":")) {
const parameterName = segment.slice(1);
if (!parameterName) return null;
segments.push({ literal: null, parameterName });
continue;
}
const literal = decodeCanonicalSegment(segment);
if (literal === null) return null;
segments.push({ literal, parameterName: null });
}
return segments;
}
function decodeCanonicalSegment(segment: string): string | null {
let decoded: string;
try {
decoded = decodeURIComponent(segment);
} catch {
return null;
}
return !decoded ||
decoded === "." ||
decoded === ".." ||
decoded.includes("/") ||
decoded.includes("\\")
? null
: decoded;
}
function compareSpecificity(
left: RouteCandidate,
right: RouteCandidate,
): number {
const sharedLength = Math.min(
left.patternSegments.length,
right.patternSegments.length,
);
for (let index = 0; index < sharedLength; index += 1) {
const leftDynamic = left.patternSegments[index]?.parameterName !== null;
const rightDynamic = right.patternSegments[index]?.parameterName !== null;
if (leftDynamic === rightDynamic) continue;
return leftDynamic ? 1 : -1;
}
return left.patternSegments.length - right.patternSegments.length;
}
function routeBelongsToDomain(
routeHref: CanonicalHousekeepingHref,
domainHref: CanonicalHousekeepingHref,
): boolean {
return (
routeHref === domainHref ||
(domainHref !== "/ase" && routeHref.startsWith(`${domainHref}/`)) ||
(domainHref === "/ase" && routeHref === "/ase")
);
}
function matchSegments(
candidate: RouteCandidate,
path: ParsedCanonicalPath,
): Record<string, string> | null {
if (candidate.patternSegments.length !== path.segments.length) return null;
const params: Record<string, string> = {};
for (const [index, patternSegment] of candidate.patternSegments.entries()) {
const pathSegment = path.segments[index] ?? "";
if (patternSegment.parameterName === null) {
if (patternSegment.literal !== pathSegment) return null;
continue;
}
if (patternSegment.parameterName in params) return null;
params[patternSegment.parameterName] = pathSegment;
}
return params;
}
@@ -1,119 +0,0 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { PERMS } from "@/lib/permission-slugs";
import type { HousekeepingCommand } from "../commands/registry";
import type {
HousekeepingCapabilityContext,
HousekeepingDomainManifest,
} from "../contracts";
import type { HousekeepingRegistry } from "../registry";
import { searchNavigationAndCommands } from "./navigation-search";
function context(granted: readonly string[]): HousekeepingCapabilityContext {
const permissions = new Set(granted);
return {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => permissions.has(slug),
hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)),
hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)),
};
}
const peopleManifest = {
id: "people",
labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description",
iconId: "users",
canonicalHref: "/ase/people",
capability: { mode: "any", slugs: [PERMS.USERS_VIEW] },
routes: [
{
id: "people.users",
href: "/ase/people/users",
labelKey: "pages.housekeeping.routes.people.users",
capability: { mode: "any", slugs: [PERMS.USERS_VIEW] },
},
{
id: "people.moderation",
href: "/ase/people/moderation",
labelKey: "pages.housekeeping.routes.people.moderation",
capability: { mode: "any", slugs: [PERMS.MODERATION_VIEW] },
},
],
searchProviders: [],
inboxSources: [],
widgets: [],
} as const satisfies HousekeepingDomainManifest;
const registry: HousekeepingRegistry = { domains: [peopleManifest] };
function command(
id: string,
permission: string,
): HousekeepingCommand<Record<string, never>, never> {
return {
id,
owner: "people",
risk: "safe",
capability: { mode: "any", slugs: [permission] },
input: z.object({}),
requiresReason: false,
rateLimit: { attempts: 3, windowMs: 60_000 },
execute: async () => ({
ok: true,
data: undefined as never,
correlationId: "command",
}),
};
}
const translations: Record<string, string> = {
"pages.housekeeping.domains.people.title": "People",
"pages.housekeeping.domains.people.description": "Accounts and moderation",
"pages.housekeeping.routes.people.users": "User accounts",
"pages.housekeeping.routes.people.moderation": "Moderation queue",
};
describe("navigation and command search", () => {
it("matches permitted domain/routes/commands and omits forbidden entries", () => {
const result = searchNavigationAndCommands({
registry,
commands: [
command("people.user.refresh", PERMS.USERS_VIEW),
command("people.moderation.ban", PERMS.MODERATION_VIEW),
],
context: context([PERMS.USERS_VIEW]),
term: "user",
translate: (key) => translations[key] ?? key,
});
expect(result.navigation.map((hit) => hit.id)).toEqual([
"route:people.users",
]);
expect(result.commands.map((hit) => hit.commandId)).toEqual([
"people.user.refresh",
]);
expect(result.navigation[0]?.href).toBe("/ase/people/users");
expect(result.commands[0]?.href).toBe("/ase/people");
});
it("normalizes terms and keeps registry/command order stable", () => {
const result = searchNavigationAndCommands({
registry,
commands: [
command("people.user.refresh", PERMS.USERS_VIEW),
command("people.user.inspect", PERMS.USERS_VIEW),
],
context: context([PERMS.USERS_VIEW]),
term: " PEOPLE ",
translate: (key) => translations[key] ?? key,
});
expect(result.navigation.map((hit) => hit.id)).toEqual(["domain:people"]);
expect(result.commands.map((hit) => hit.commandId)).toEqual([
"people.user.refresh",
"people.user.inspect",
]);
});
});
@@ -1,149 +0,0 @@
import { satisfiesCapability } from "../capability-context";
import type {
CanonicalHousekeepingHref,
CapabilityRequirement,
HousekeepingCapabilityContext,
HousekeepingSearchResult,
} from "../contracts";
import type { HousekeepingRegistry } from "../registry";
export interface HousekeepingNavigationHit
extends Omit<HousekeepingSearchResult, "type"> {
readonly type: "navigation";
}
export interface HousekeepingCommandHit
extends Omit<HousekeepingSearchResult, "type"> {
readonly type: "command";
readonly commandId: string;
readonly risk: "safe" | "sensitive";
readonly requiresReason: boolean;
}
export interface DiscoverableHousekeepingCommand {
readonly id: string;
readonly owner: HousekeepingCommandHit["domain"];
readonly risk: HousekeepingCommandHit["risk"];
readonly capability: CapabilityRequirement;
readonly requiresReason: boolean;
}
export interface NavigationAndCommandSearchInput {
readonly registry: HousekeepingRegistry;
readonly commands: readonly DiscoverableHousekeepingCommand[];
readonly context: HousekeepingCapabilityContext;
readonly term: string;
readonly translate: (key: string) => string;
}
export interface NavigationAndCommandSearchResult {
readonly navigation: readonly HousekeepingNavigationHit[];
readonly commands: readonly HousekeepingCommandHit[];
}
export function normalizeHousekeepingSearchTerm(term: string): string {
return term.normalize("NFC").trim().slice(0, 128);
}
function searchTokens(term: string): readonly string[] {
return normalizeHousekeepingSearchTerm(term)
.toLocaleLowerCase()
.split(/\s+/u)
.filter(Boolean);
}
function matches(tokens: readonly string[], ...values: readonly string[]) {
if (tokens.length === 0) return true;
const haystack = values.join(" ").normalize("NFC").toLocaleLowerCase();
return tokens.every((token) => haystack.includes(token));
}
function commandTitle(
commandId: string,
translate: (key: string) => string,
): string {
const key = `pages.housekeeping.commands.${commandId}`;
const translated = translate(key);
if (translated !== key) return translated;
return commandId
.split(".")
.slice(1)
.flatMap((segment) => segment.split("-"))
.map((segment) =>
segment.length === 0
? segment
: `${segment[0]?.toLocaleUpperCase()}${segment.slice(1)}`,
)
.join(" ");
}
export function searchNavigationAndCommands({
registry,
commands,
context,
term,
translate,
}: NavigationAndCommandSearchInput): NavigationAndCommandSearchResult {
const tokens = searchTokens(term);
const navigation: HousekeepingNavigationHit[] = [];
for (const domain of registry.domains) {
if (!satisfiesCapability(context, domain.capability)) continue;
const domainTitle = translate(domain.labelKey);
const domainDescription = translate(domain.descriptionKey);
if (matches(tokens, domain.id, domainTitle, domainDescription)) {
navigation.push({
id: `domain:${domain.id}`,
domain: domain.id,
type: "navigation",
title: domainTitle,
description: domainDescription,
href: domain.canonicalHref,
capability: domain.capability,
});
}
for (const route of domain.routes) {
if (!satisfiesCapability(context, route.capability)) continue;
const title = translate(route.labelKey);
const localRouteId = route.id.split(".").slice(1).join(" ");
if (!matches(tokens, localRouteId, title)) continue;
navigation.push({
id: `route:${route.id}`,
domain: domain.id,
type: "navigation",
title,
href: route.href,
capability: route.capability,
});
}
}
const commandsFound: HousekeepingCommandHit[] = [];
for (const command of commands) {
if (!satisfiesCapability(context, command.capability)) continue;
const domain = registry.domains.find(
(candidate) => candidate.id === command.owner,
);
if (!domain || !satisfiesCapability(context, domain.capability)) continue;
const title = commandTitle(command.id, translate);
if (!matches(tokens, command.id, command.owner, title)) continue;
commandsFound.push({
id: `command:${command.id}`,
commandId: command.id,
domain: command.owner,
type: "command",
title,
description: command.id,
href: domain.canonicalHref as CanonicalHousekeepingHref,
capability: command.capability,
risk: command.risk,
requiresReason: command.requiresReason,
});
}
return {
navigation: navigation.slice(0, 50),
commands: commandsFound.slice(0, 50),
};
}
@@ -1,198 +0,0 @@
import { describe, expect, it, vi } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import {
anyCapability,
fail,
type HousekeepingCapabilityContext,
type HousekeepingDomainManifest,
type HousekeepingSearchProvider,
ok,
} from "../contracts";
import type { HousekeepingRegistry } from "../registry";
import {
createHousekeepingSearchService,
SEARCH_POLICY,
} from "./search-service";
function context(granted: readonly string[]): HousekeepingCapabilityContext {
const permissions = new Set(granted);
return {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => permissions.has(slug),
hasAny: (...slugs) => slugs.some((slug) => permissions.has(slug)),
hasAll: (...slugs) => slugs.every((slug) => permissions.has(slug)),
};
}
function provider(
id: string,
permission: string,
search: HousekeepingSearchProvider["search"],
): HousekeepingSearchProvider {
return {
id,
owner: "people",
capability: anyCapability(permission),
search,
};
}
function registry(
searchProviders: readonly HousekeepingSearchProvider[],
): HousekeepingRegistry {
return {
domains: [
{
id: "people",
labelKey: "pages.housekeeping.domains.people.title",
descriptionKey: "pages.housekeeping.domains.people.description",
iconId: "users",
canonicalHref: "/ase/people",
capability: anyCapability(PERMS.USERS_VIEW),
routes: [
{
id: "people.users",
href: "/ase/people/users",
labelKey: "pages.housekeeping.routes.people.users",
capability: anyCapability(PERMS.USERS_VIEW),
},
],
searchProviders,
inboxSources: [],
widgets: [],
} satisfies HousekeepingDomainManifest,
],
};
}
function entity(id: string) {
return {
id,
domain: "people" as const,
type: "entity" as const,
title: `User ${id}`,
href: "/ase/people/users" as const,
capability: anyCapability(PERMS.USERS_VIEW),
};
}
describe("Housekeeping search service", () => {
it("locks the provider timeout and result limits", () => {
expect(SEARCH_POLICY).toEqual({
minimumEntityTermLength: 2,
timeoutMs: 2_000,
providerLimit: 25,
combinedEntityLimit: 50,
});
});
it("searches navigation/commands only below two trimmed characters", async () => {
const search = vi.fn(async () => ok([entity("1")], "provider"));
const service = createHousekeepingSearchService({
registry: registry([provider("people.users", PERMS.USERS_VIEW, search)]),
commands: [],
translate: (key) =>
key.endsWith("people.users") ? "Accounts" : "People",
createCorrelationId: () => "search-short",
});
const response = await service(" a ", context([PERMS.USERS_VIEW]));
expect(search).not.toHaveBeenCalled();
expect(response.navigation.map((hit) => hit.title)).toContain("Accounts");
expect(response.entities).toEqual([]);
expect(response.correlationId).toBe("search-short");
});
it("invokes only permitted providers for two-character terms", async () => {
const permitted = vi.fn(async () => ok([entity("1")], "permitted"));
const forbidden = vi.fn(async () => ok([entity("2")], "forbidden"));
const service = createHousekeepingSearchService({
registry: registry([
provider("people.users", PERMS.USERS_VIEW, permitted),
provider("people.bans", PERMS.BANS_VIEW, forbidden),
]),
commands: [],
translate: (key) => key,
createCorrelationId: () => "search-two",
});
const response = await service("ab", context([PERMS.USERS_VIEW]));
expect(permitted).toHaveBeenCalledWith(expect.any(Object), {
term: "ab",
limit: 25,
});
expect(forbidden).not.toHaveBeenCalled();
expect(response.entities.map((item) => item.id)).toEqual(["1"]);
});
it("does not invoke providers owned by a forbidden domain", async () => {
const search = vi.fn(async () => ok([entity("1")], "forbidden-domain"));
const service = createHousekeepingSearchService({
registry: registry([provider("people.bans", PERMS.BANS_VIEW, search)]),
commands: [],
translate: (key) => key,
createCorrelationId: () => "search-forbidden-domain",
});
const response = await service("ab", context([PERMS.BANS_VIEW]));
expect(search).not.toHaveBeenCalled();
expect(response.entities).toEqual([]);
});
it("caps providers at 25, deduplicates stably, and caps combined entities at 50", async () => {
const ranges = [
Array.from({ length: 30 }, (_, index) => entity(String(index))),
Array.from({ length: 30 }, (_, index) => entity(String(index + 20))),
Array.from({ length: 30 }, (_, index) => entity(String(index + 45))),
];
const service = createHousekeepingSearchService({
registry: registry(
ranges.map((items, index) =>
provider(`people.${index}`, PERMS.USERS_VIEW, async () =>
ok(items, `provider-${index}`),
),
),
),
commands: [],
translate: (key) => key,
createCorrelationId: () => "search-limits",
});
const response = await service("user", context([PERMS.USERS_VIEW]));
expect(response.entities).toHaveLength(50);
expect(response.entities.map((item) => item.id)).toEqual(
Array.from({ length: 50 }, (_, index) => String(index)),
);
});
it("returns successful entities with stable typed timeout/provider errors", async () => {
const never = new Promise<never>(() => undefined);
const service = createHousekeepingSearchService({
registry: registry([
provider("people.timeout", PERMS.USERS_VIEW, () => never),
provider("people.failed", PERMS.USERS_VIEW, async () =>
fail(
"DEPENDENCY_UNAVAILABLE",
"errors.housekeeping.dependencyUnavailable",
"failed",
),
),
provider("people.ready", PERMS.USERS_VIEW, async () =>
ok([entity("ready")], "ready"),
),
]),
commands: [],
translate: (key) => key,
createCorrelationId: () => "search-partial",
timeoutMs: 5,
});
const response = await service("user", context([PERMS.USERS_VIEW]));
expect(response.entities.map((item) => item.id)).toEqual(["ready"]);
expect(response.errors).toEqual([
{ providerId: "people.timeout", code: "TIMEOUT" },
{ providerId: "people.failed", code: "DEPENDENCY_UNAVAILABLE" },
]);
});
});
@@ -1,210 +0,0 @@
import "server-only";
import { satisfiesCapability } from "../capability-context";
import { listHousekeepingCommands } from "../commands/registry";
import type {
HousekeepingCapabilityContext,
HousekeepingErrorCode,
HousekeepingSearchProvider,
HousekeepingSearchResult,
} from "../contracts";
import { createCorrelationId } from "../correlation";
import { isSafeHousekeepingHref } from "../housekeeping-href";
import {
createHousekeepingRegistry,
type HousekeepingRegistry,
} from "../registry";
import {
type DiscoverableHousekeepingCommand,
type HousekeepingCommandHit,
type HousekeepingNavigationHit,
normalizeHousekeepingSearchTerm,
searchNavigationAndCommands,
} from "./navigation-search";
export const SEARCH_POLICY = Object.freeze({
minimumEntityTermLength: 2,
timeoutMs: 2_000,
providerLimit: 25,
combinedEntityLimit: 50,
} as const);
export interface HousekeepingSearchResponse {
readonly navigation: readonly HousekeepingNavigationHit[];
readonly commands: readonly HousekeepingCommandHit[];
readonly entities: readonly HousekeepingSearchResult[];
readonly errors: readonly {
providerId: string;
code: HousekeepingErrorCode;
}[];
readonly correlationId: string;
}
export interface HousekeepingSearchServiceDependencies {
readonly registry: HousekeepingRegistry;
readonly commands: readonly DiscoverableHousekeepingCommand[];
readonly translate: (key: string) => string;
readonly createCorrelationId?: () => string;
readonly timeoutMs?: number;
}
type ProviderOutcome = {
readonly providerId: string;
readonly items: readonly HousekeepingSearchResult[];
readonly error?: HousekeepingErrorCode;
};
function validEntityResult(
item: HousekeepingSearchResult,
provider: HousekeepingSearchProvider,
context: HousekeepingCapabilityContext,
): boolean {
return (
item !== null &&
typeof item === "object" &&
item.type === "entity" &&
item.domain === provider.owner &&
typeof item.id === "string" &&
item.id.trim().length > 0 &&
typeof item.title === "string" &&
item.title.trim().length > 0 &&
typeof item.href === "string" &&
isSafeHousekeepingHref(item.href) &&
satisfiesCapability(context, item.capability)
);
}
async function runProvider(
provider: HousekeepingSearchProvider,
context: HousekeepingCapabilityContext,
term: string,
timeoutMs: number,
): Promise<ProviderOutcome> {
let timeout: ReturnType<typeof setTimeout> | undefined;
const timedOut = new Promise<ProviderOutcome>((resolve) => {
timeout = setTimeout(
() =>
resolve({
providerId: provider.id,
items: [],
error: "TIMEOUT",
}),
timeoutMs,
);
});
const searched = provider
.search(context, { term, limit: SEARCH_POLICY.providerLimit })
.then<ProviderOutcome>((result) => {
if (!result.ok) {
return {
providerId: provider.id,
items: [],
error: result.error.code,
};
}
return {
providerId: provider.id,
items: result.data
.filter((item) => validEntityResult(item, provider, context))
.slice(0, SEARCH_POLICY.providerLimit),
};
})
.catch<ProviderOutcome>(() => ({
providerId: provider.id,
items: [],
error: "INTERNAL",
}));
try {
return await Promise.race([searched, timedOut]);
} finally {
if (timeout !== undefined) clearTimeout(timeout);
}
}
export function createHousekeepingSearchService({
registry,
commands,
translate,
createCorrelationId: correlationIdFactory = createCorrelationId,
timeoutMs = SEARCH_POLICY.timeoutMs,
}: HousekeepingSearchServiceDependencies) {
return async function runSearch(
term: string,
context: HousekeepingCapabilityContext,
): Promise<HousekeepingSearchResponse> {
const normalizedTerm = normalizeHousekeepingSearchTerm(term);
const navigationAndCommands = searchNavigationAndCommands({
registry,
commands,
context,
term: normalizedTerm,
translate,
});
if (normalizedTerm.length < SEARCH_POLICY.minimumEntityTermLength) {
return {
...navigationAndCommands,
entities: [],
errors: [],
correlationId: correlationIdFactory(),
};
}
const providers = registry.domains.flatMap((domain) => {
if (!satisfiesCapability(context, domain.capability)) return [];
return domain.searchProviders.filter((provider) =>
satisfiesCapability(context, provider.capability),
);
});
const outcomes = await Promise.all(
providers.map((provider) =>
runProvider(provider, context, normalizedTerm, timeoutMs),
),
);
const seen = new Set<string>();
const entities: HousekeepingSearchResult[] = [];
for (const outcome of outcomes) {
for (const item of outcome.items) {
const key = `${item.domain}:${item.type}:${item.id}`;
if (seen.has(key)) continue;
seen.add(key);
entities.push(item);
if (entities.length === SEARCH_POLICY.combinedEntityLimit) break;
}
if (entities.length === SEARCH_POLICY.combinedEntityLimit) break;
}
return {
...navigationAndCommands,
entities,
errors: outcomes.flatMap((outcome) =>
outcome.error
? [{ providerId: outcome.providerId, code: outcome.error }]
: [],
),
correlationId: correlationIdFactory(),
};
};
}
const MESSAGE_PREFIX = "pages.housekeeping.";
export async function searchHousekeeping(
term: string,
context: HousekeepingCapabilityContext,
): Promise<HousekeepingSearchResponse> {
const [{ getTranslations }, { HOUSEKEEPING_MANIFESTS }] = await Promise.all([
import("next-intl/server"),
import("../../manifests"),
]);
const translate = await getTranslations("pages.housekeeping");
const service = createHousekeepingSearchService({
registry: createHousekeepingRegistry(HOUSEKEEPING_MANIFESTS),
commands: listHousekeepingCommands(),
translate: (key) => {
if (!key.startsWith(MESSAGE_PREFIX)) return key;
return translate(key.slice(MESSAGE_PREFIX.length) as never);
},
});
return service(term, context);
}
@@ -1,138 +1,44 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const {
auth,
getAdminContext,
getTranslations,
notFound,
redirect,
startLogicalRequest,
staleActor,
wrapCache,
} = vi.hoisted(() => {
let requestCache = new Map<symbol, Promise<unknown>>();
return {
auth: vi.fn(),
getAdminContext: vi.fn(),
getTranslations: vi.fn(),
notFound: vi.fn((): never => {
throw new Error("NEXT_NOT_FOUND");
}),
redirect: vi.fn((href: string): never => {
throw new Error(`NEXT_REDIRECT:${href}`);
}),
startLogicalRequest: () => {
requestCache = new Map();
},
staleActor: { id: 9, username: "stale-session", rank: 1 },
wrapCache: (callback: () => Promise<unknown>) => {
const cacheKey = Symbol("housekeeping-capability-context");
return () => {
let value = requestCache.get(cacheKey);
if (!value) {
value = callback();
requestCache.set(cacheKey, value);
}
return value;
};
},
};
});
vi.mock("react", async (importOriginal) => ({
...(await importOriginal<typeof import("react")>()),
cache: wrapCache,
const { auth, getAdminContext, staleActor } = vi.hoisted(() => ({
auth: vi.fn(),
getAdminContext: vi.fn(),
staleActor: { id: 9, username: "stale-session", rank: 1 },
}));
vi.mock("next/navigation", () => ({ notFound, redirect }));
vi.mock("next-intl/server", () => ({ getTranslations }));
vi.mock("react", () => ({
cache: <T extends (...args: never[]) => unknown>(callback: T) => callback,
}));
vi.mock("@/lib/auth", () => ({ auth }));
vi.mock("@/lib/db", () => {
throw new Error("server capability context must not access the database");
});
vi.mock("@/lib/permissions", () => ({ getAdminContext }));
import AdminNextDomainLayout from "@/app/ase/[domain]/layout";
import AdminNextPage from "@/app/ase/page";
import { requireHousekeepingCapability } from "@/lib/admin/guard";
import { auth as mockedAuth } from "@/lib/auth";
import { anyCapability } from "./contracts";
import { getHousekeepingCapabilityContext } from "./server-capability-context";
function adminContext(
actor: { id: number; username: string; rank: number },
granted: readonly string[],
) {
const permissions = new Set(granted);
return {
session: { user: actor },
permissions: {
isSuperAdmin: false,
has: (slug: string) => permissions.has(slug),
hasAny: (...slugs: string[]) =>
slugs.some((slug) => permissions.has(slug)),
hasAll: (...slugs: string[]) =>
slugs.every((slug) => permissions.has(slug)),
},
};
}
async function invokeRequestConsumers() {
const shell = await AdminNextDomainLayout({
children: null,
params: Promise.resolve({ domain: "operations" }),
});
const page = await AdminNextPage();
const commandContext = await requireHousekeepingCapability(
anyCapability("admin.dashboard"),
);
return { commandContext, page, shell };
}
describe("getHousekeepingCapabilityContext", () => {
beforeEach(() => {
startLogicalRequest();
vi.clearAllMocks();
auth.mockResolvedValue({ user: staleActor });
getTranslations.mockResolvedValue((key: string) => key);
getAdminContext.mockResolvedValue(
adminContext({ id: 42, username: "operator", rank: 7 }, [
"admin.dashboard",
"admin.users.view",
]),
);
});
it("isolates real shell, page, and command preflight consumers between logical requests", async () => {
const first = await invokeRequestConsumers();
expect(first.shell).toBeDefined();
expect(first.page).toBeDefined();
expect(first.commandContext.actor).toEqual({
id: 42,
username: "operator",
rank: 7,
getAdminContext.mockResolvedValue({
session: {
user: {
id: 42,
username: "operator",
rank: 7,
},
},
permissions: {
isSuperAdmin: false,
has: (slug: string) => slug === "admin.users.view",
hasAny: (...slugs: string[]) => slugs.includes("admin.users.view"),
hasAll: (...slugs: string[]) =>
slugs.every((slug) => slug === "admin.users.view"),
},
});
expect(getAdminContext).toHaveBeenCalledTimes(1);
startLogicalRequest();
getAdminContext.mockResolvedValue(
adminContext({ id: 84, username: "second-operator", rank: 2 }, [
"admin.dashboard",
]),
);
const second = await invokeRequestConsumers();
expect(second.shell).toBeDefined();
expect(second.page).toBeDefined();
expect(second.commandContext.actor).toEqual({
id: 84,
username: "second-operator",
rank: 2,
});
expect(second.commandContext.has("admin.users.view")).toBe(false);
expect(getAdminContext).toHaveBeenCalledTimes(2);
});
it("uses the refreshed administrator actor instead of the stale auth actor", async () => {
@@ -1,121 +0,0 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";
import {
CommandDeck,
commandDeckKeyAction,
flattenHousekeepingSearchResults,
isCommandDeckShortcut,
nextCommandDeckIndex,
projectHousekeepingSearchHref,
restoreCommandDeckFocus,
} from "./command-deck";
vi.mock("@/actions/housekeeping-search", () => ({
executeHousekeepingSearch: vi.fn(),
}));
const labels = {
trigger: "Search housekeeping",
placeholder: "Search navigation, commands, and entities",
navigation: "Navigation",
commands: "Commands",
entities: "Entities",
loading: "Searching…",
empty: "No results",
partial: "Some providers are unavailable",
close: "Close",
};
describe("CommandDeck", () => {
it("renders an enabled accessible cmdk trigger", () => {
const html = renderToStaticMarkup(
<CommandDeck labels={labels} search={vi.fn()} />,
);
expect(html).toContain("Search housekeeping");
expect(html).toContain('aria-haspopup="dialog"');
expect(html).not.toContain("disabled");
expect(html).toContain("Ctrl+K");
});
it("models shortcut, arrows, Enter, and Escape deterministically", () => {
expect(
isCommandDeckShortcut({ key: "k", ctrlKey: true, metaKey: false }),
).toBe(true);
expect(
isCommandDeckShortcut({ key: "K", ctrlKey: false, metaKey: true }),
).toBe(true);
expect(
isCommandDeckShortcut({
key: "k",
ctrlKey: true,
metaKey: false,
altKey: true,
}),
).toBe(false);
expect(commandDeckKeyAction("Escape", true)).toBe("close");
expect(commandDeckKeyAction("ArrowDown", true)).toBe("next");
expect(commandDeckKeyAction("ArrowUp", true)).toBe("previous");
expect(commandDeckKeyAction("Enter", true)).toBe("activate");
expect(commandDeckKeyAction("Enter", false)).toBe(null);
expect(nextCommandDeckIndex(-1, 3, "next")).toBe(0);
expect(nextCommandDeckIndex(2, 3, "next")).toBe(0);
expect(nextCommandDeckIndex(0, 3, "previous")).toBe(2);
});
it("restores trigger focus and keeps canonical result hrefs", () => {
const focus = vi.fn();
restoreCommandDeckFocus({ focus });
expect(focus).toHaveBeenCalledOnce();
expect(projectHousekeepingSearchHref("/ase/people/users")).toBe(
"/ase/people/users",
);
});
it("keeps grouped result order and uses cmdk input/items in the client module", () => {
const flattened = flattenHousekeepingSearchResults({
navigation: [
{
id: "route:people.users",
domain: "people",
type: "navigation",
title: "Users",
href: "/ase/people/users",
capability: { mode: "any", slugs: ["users.view"] },
},
],
commands: [
{
id: "command:people.user.refresh",
commandId: "people.user.refresh",
domain: "people",
type: "command",
title: "User Refresh",
href: "/ase/people",
capability: { mode: "any", slugs: ["users.view"] },
risk: "safe",
requiresReason: false,
},
],
entities: [],
errors: [],
correlationId: "deck",
});
expect(flattened.map((entry) => entry.group)).toEqual([
"navigation",
"commands",
]);
const source = readFileSync(
resolve(
process.cwd(),
"src/features/housekeeping/foundation/shell/command-deck.tsx",
),
"utf8",
);
expect(source).toContain("CommandInput");
expect(source).toContain("CommandItem");
expect(source).toContain("onSelect");
});
});
@@ -1,343 +0,0 @@
"use client";
import {
type KeyboardEvent,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import type { HousekeepingSearchResult } from "../contracts";
import { toHousekeepingHref } from "../routing/href";
import type {
HousekeepingCommandHit,
HousekeepingNavigationHit,
} from "../search/navigation-search";
import type { HousekeepingSearchResponse } from "../search/search-service";
export interface CommandDeckLabels {
readonly trigger: string;
readonly placeholder: string;
readonly navigation: string;
readonly commands: string;
readonly entities: string;
readonly loading: string;
readonly empty: string;
readonly partial: string;
readonly close: string;
}
export type HousekeepingSearchExecutor = (
term: string,
) => Promise<HousekeepingSearchResponse>;
type CommandDeckHit =
| HousekeepingNavigationHit
| HousekeepingCommandHit
| HousekeepingSearchResult;
type CommandDeckGroup = "navigation" | "commands" | "entities";
export type CommandDeckKeyboardAction =
| "close"
| "next"
| "previous"
| "activate"
| null;
export interface CommandDeckEntry {
readonly group: CommandDeckGroup;
readonly value: string;
readonly hit: CommandDeckHit;
}
const EMPTY_RESPONSE: HousekeepingSearchResponse = {
navigation: [],
commands: [],
entities: [],
errors: [],
correlationId: "initial-search",
};
export function isCommandDeckShortcut(input: {
readonly key: string;
readonly ctrlKey: boolean;
readonly metaKey: boolean;
readonly altKey?: boolean;
readonly shiftKey?: boolean;
}): boolean {
return (
input.key.toLocaleLowerCase() === "k" &&
(input.ctrlKey || input.metaKey) &&
input.altKey !== true &&
input.shiftKey !== true
);
}
export function commandDeckKeyAction(
key: string,
open: boolean,
): CommandDeckKeyboardAction {
if (!open) return null;
if (key === "Escape") return "close";
if (key === "ArrowDown") return "next";
if (key === "ArrowUp") return "previous";
if (key === "Enter") return "activate";
return null;
}
export function nextCommandDeckIndex(
current: number,
count: number,
direction: "next" | "previous",
): number {
if (count <= 0) return -1;
if (direction === "next") return (current + 1 + count) % count;
return (current - 1 + count) % count;
}
export function restoreCommandDeckFocus(
target: {
focus(): void;
} | null,
): void {
target?.focus();
}
export function projectHousekeepingSearchHref(
href: HousekeepingNavigationHit["href"],
) {
return toHousekeepingHref(href);
}
export function flattenHousekeepingSearchResults(
response: HousekeepingSearchResponse,
): readonly CommandDeckEntry[] {
const group = (
name: CommandDeckGroup,
hits: readonly CommandDeckHit[],
): readonly CommandDeckEntry[] =>
hits.map((hit) => ({
group: name,
value: `${name}:${hit.domain}:${hit.id}`,
hit,
}));
return [
...group("navigation", response.navigation),
...group("commands", response.commands),
...group("entities", response.entities),
];
}
export function CommandDeck({
labels,
search,
}: {
readonly labels: CommandDeckLabels;
readonly search: HousekeepingSearchExecutor;
}) {
const [open, setOpen] = useState(false);
const [term, setTerm] = useState("");
const [response, setResponse] =
useState<HousekeepingSearchResponse>(EMPTY_RESPONSE);
const [loading, setLoading] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(-1);
const triggerRef = useRef<HTMLButtonElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const requestSequence = useRef(0);
const wasOpen = useRef(false);
const entries = useMemo(
() => flattenHousekeepingSearchResults(response),
[response],
);
const close = useCallback(() => setOpen(false), []);
useEffect(() => {
const onShortcut = (event: globalThis.KeyboardEvent) => {
if (!isCommandDeckShortcut(event)) return;
event.preventDefault();
setOpen((current) => !current);
};
document.addEventListener("keydown", onShortcut);
return () => document.removeEventListener("keydown", onShortcut);
}, []);
useEffect(() => {
if (open) {
wasOpen.current = true;
inputRef.current?.focus();
return;
}
if (wasOpen.current) {
wasOpen.current = false;
restoreCommandDeckFocus(triggerRef.current);
}
}, [open]);
useEffect(() => {
if (!open) return;
const sequence = ++requestSequence.current;
let active = true;
setLoading(true);
const timer = setTimeout(() => {
search(term)
.then((nextResponse) => {
if (active && sequence === requestSequence.current) {
setResponse(nextResponse);
}
})
.catch(() => {
if (active && sequence === requestSequence.current) {
setResponse({
...EMPTY_RESPONSE,
errors: [{ providerId: "search", code: "INTERNAL" }],
correlationId: "client-search",
});
}
})
.finally(() => {
if (active && sequence === requestSequence.current) setLoading(false);
});
}, 120);
return () => {
active = false;
clearTimeout(timer);
};
}, [open, search, term]);
useEffect(() => {
setSelectedIndex(entries.length === 0 ? -1 : 0);
}, [entries.length]);
const activate = useCallback(
(entry: CommandDeckEntry) => {
const href = projectHousekeepingSearchHref(entry.hit.href);
close();
window.location.assign(href);
},
[close],
);
const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
const action = commandDeckKeyAction(event.key, open);
if (action === null) return;
event.preventDefault();
event.stopPropagation();
if (action === "close") {
close();
return;
}
if (action === "activate") {
const selected = entries[selectedIndex];
if (selected) activate(selected);
return;
}
setSelectedIndex((current) =>
nextCommandDeckIndex(current, entries.length, action),
);
};
const selectedValue = entries[selectedIndex]?.value ?? "";
const renderGroup = (group: CommandDeckGroup, heading: string) => {
const groupedEntries = entries.filter((entry) => entry.group === group);
if (groupedEntries.length === 0) return null;
return (
<CommandGroup heading={heading}>
{groupedEntries.map((entry) => (
<CommandItem
key={entry.value}
value={entry.value}
onSelect={() => activate(entry)}
>
<span className="min-w-0 flex-1 truncate">{entry.hit.title}</span>
<span className="text-xs text-[var(--admin-text-muted)]">
{entry.hit.domain}
</span>
</CommandItem>
))}
</CommandGroup>
);
};
return (
<>
<button
ref={triggerRef}
type="button"
aria-haspopup="dialog"
aria-expanded={open}
onClick={() => setOpen(true)}
className="flex min-w-0 items-center gap-2 rounded-md border border-[var(--admin-border)] bg-[var(--admin-surface)] px-3 py-2 text-sm text-[var(--admin-text)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
<span>{labels.trigger}</span>
<kbd className="text-xs text-[var(--admin-text-muted)]">Ctrl+K</kbd>
</button>
{open ? (
<div
role="dialog"
aria-modal="true"
aria-label={labels.trigger}
className="fixed inset-0 z-50 flex items-start justify-center bg-[var(--admin-canvas)]/80 p-2 sm:p-4 pt-4 sm:pt-[10vh]"
>
<div className="max-h-[calc(100dvh-2rem)] min-w-0 w-full max-w-2xl overflow-hidden rounded-lg border border-[var(--admin-border)] bg-[var(--admin-surface)] shadow-xl">
<div className="flex justify-end border-b border-[var(--admin-border)] p-2">
<button
type="button"
onClick={close}
className="rounded px-2 py-1 text-sm text-[var(--admin-text-muted)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
{labels.close}
</button>
</div>
<Command
shouldFilter={false}
value={selectedValue}
onValueChange={(value) => {
const index = entries.findIndex(
(entry) => entry.value === value,
);
if (index >= 0) setSelectedIndex(index);
}}
onKeyDownCapture={onKeyDown}
>
<CommandInput
ref={inputRef}
value={term}
onValueChange={setTerm}
placeholder={labels.placeholder}
aria-label={labels.placeholder}
/>
<CommandList
aria-busy={loading}
className="min-w-0 overflow-y-auto"
>
{loading ? <p role="status">{labels.loading}</p> : null}
{response.errors.length > 0 ? (
<p
role="alert"
aria-live="assertive"
className="px-3 py-2 text-sm"
>
{labels.partial}
</p>
) : null}
{!loading ? <CommandEmpty>{labels.empty}</CommandEmpty> : null}
{renderGroup("navigation", labels.navigation)}
{renderGroup("commands", labels.commands)}
{renderGroup("entities", labels.entities)}
</CommandList>
</Command>
</div>
</div>
) : null}
</>
);
}
@@ -1,15 +1,15 @@
import {
CommandDeck,
type CommandDeckLabels,
type HousekeepingSearchExecutor,
} from "./command-deck";
interface CommandTriggerProps {
label: string;
labels: Omit<CommandDeckLabels, "trigger">;
search: HousekeepingSearchExecutor;
}
export function CommandTrigger({ label, labels, search }: CommandTriggerProps) {
return <CommandDeck labels={{ trigger: label, ...labels }} search={search} />;
export function CommandTrigger({ label }: CommandTriggerProps) {
return (
<button
type="button"
disabled
className="rounded-md border border-[var(--admin-border)] bg-[var(--admin-surface)] px-3 py-2 text-sm text-[var(--admin-text-muted)]"
>
{label}
</button>
);
}
@@ -7,17 +7,13 @@ interface ContextNavProps {
export function ContextNav({ items, ariaLabel }: ContextNavProps) {
return (
<nav
aria-label={ariaLabel}
data-housekeeping-landmark="context-navigation"
className="min-w-0"
>
<nav aria-label={ariaLabel} className="min-w-0">
<ul className="flex gap-2 overflow-x-auto pb-1 lg:flex-col lg:overflow-visible">
{items.map((item) => (
<li key={item.id} className="min-w-max">
<Link
href={item.href}
className="block min-h-11 rounded-md px-3 py-2 text-sm text-[var(--admin-text-muted)] hover:bg-[var(--admin-canvas)] hover:text-[var(--admin-text)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
className="block rounded-md px-3 py-2 text-sm text-[var(--admin-text-muted)] hover:bg-[var(--admin-canvas)] hover:text-[var(--admin-text)]"
>
{item.label}
</Link>
@@ -27,18 +27,14 @@ export function DomainRail({
ariaLabel,
}: DomainRailProps) {
return (
<nav
aria-label={ariaLabel}
data-housekeeping-landmark="primary-navigation"
className="min-w-0 w-full lg:w-20"
>
<nav aria-label={ariaLabel} className="w-full lg:w-20">
<ul className="flex gap-1 overflow-x-auto lg:flex-col lg:overflow-visible">
{domains.map((domain) => (
<li key={domain.id} className="min-w-max lg:min-w-0">
<Link
href={domain.href}
aria-current={domain.id === activeDomainId ? "page" : undefined}
className="flex min-h-11 items-center gap-2 rounded-lg px-3 py-2 text-sm text-[var(--admin-text-muted)] hover:bg-[var(--admin-surface)] hover:text-[var(--admin-text)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)] lg:justify-center"
className="flex items-center gap-2 rounded-lg px-3 py-2 text-sm text-[var(--admin-text-muted)] hover:bg-[var(--admin-surface)] hover:text-[var(--admin-text)] lg:justify-center"
>
<span data-domain-icon={domain.iconId}>
<DomainIcon iconId={domain.iconId} />
@@ -1,248 +0,0 @@
"use client";
import {
DndContext,
type DragEndEvent,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useState } from "react";
import Link from "@/components/link";
import type { HousekeepingRouteDefinition } from "../contracts";
import type { HousekeepingPreferences } from "../preferences/schema";
import { toHousekeepingHref } from "../routing/href";
import {
type HousekeepingPreferenceSaver,
preferencesAfterSave,
} from "./widget-settings";
export function movePreferenceId(
ids: readonly string[],
activeId: string,
overId: string,
): string[] {
const from = ids.indexOf(activeId);
const to = ids.indexOf(overId);
if (from < 0 || to < 0 || from === to) return [...ids];
return arrayMove([...ids], from, to);
}
export function togglePinnedRoute(
preferences: HousekeepingPreferences,
routeId: string,
): HousekeepingPreferences {
const pinned = preferences.pinnedRouteIds.includes(routeId);
if (pinned) {
return {
...preferences,
pinnedRouteIds: preferences.pinnedRouteIds.filter((id) => id !== routeId),
shortcutOrder: preferences.shortcutOrder.filter((id) => id !== routeId),
};
}
return {
...preferences,
pinnedRouteIds: [...preferences.pinnedRouteIds, routeId],
shortcutOrder: preferences.shortcutOrder.includes(routeId)
? [...preferences.shortcutOrder]
: [...preferences.shortcutOrder, routeId],
};
}
function orderedPinnedRouteIds(preferences: HousekeepingPreferences): string[] {
const pinned = new Set(preferences.pinnedRouteIds);
const ordered = preferences.shortcutOrder.filter((id) => pinned.has(id));
for (const id of preferences.pinnedRouteIds) {
if (!ordered.includes(id)) ordered.push(id);
}
return ordered;
}
function SortableFavorite({
route,
title,
unpin,
unpinLabel,
reorderInstructionsId,
}: {
readonly route: HousekeepingRouteDefinition;
readonly title: string;
readonly unpin: () => void;
readonly unpinLabel: string;
readonly reorderInstructionsId: string;
}) {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({
id: route.id,
});
return (
<li
ref={setNodeRef}
style={{ transform: CSS.Transform.toString(transform), transition }}
className="flex min-w-0 items-center gap-2 rounded-md border border-[var(--admin-border)] p-3 motion-reduce:transition-none"
>
<button
type="button"
{...attributes}
{...listeners}
aria-label={title}
aria-describedby={reorderInstructionsId}
className="cursor-grab rounded text-[var(--admin-text-muted)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
↕
</button>
<Link
className="min-w-0 flex-1 break-words rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
href={toHousekeepingHref(route.href)}
>
{title}
</Link>
<button
type="button"
onClick={unpin}
className="rounded-md px-2 py-1 text-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
{unpinLabel}
</button>
</li>
);
}
export interface FavoritesLabels {
readonly title: string;
readonly pin: string;
readonly unpin: string;
readonly error: string;
readonly reorderInstructions: string;
}
export function Favorites({
preferences: initialPreferences,
routes,
savePreferences,
labels,
titles = {},
}: {
readonly preferences: HousekeepingPreferences;
readonly routes: readonly HousekeepingRouteDefinition[];
readonly savePreferences: HousekeepingPreferenceSaver;
readonly labels: FavoritesLabels;
readonly titles?: Readonly<Record<string, string>>;
}) {
const [preferences, setPreferences] = useState(initialPreferences);
const [error, setError] = useState(false);
const [saving, setSaving] = useState(false);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
const routeById = new Map(routes.map((route) => [route.id, route]));
const pinnedIds = orderedPinnedRouteIds(preferences).filter((id) =>
routeById.has(id),
);
const unpinned = routes.filter(
(route) => !preferences.pinnedRouteIds.includes(route.id),
);
const commit = async (proposed: HousekeepingPreferences) => {
setSaving(true);
setError(false);
try {
const outcome = preferencesAfterSave(
preferences,
proposed,
await savePreferences(proposed),
);
setPreferences(outcome.preferences);
setError(outcome.error !== undefined);
} catch {
setError(true);
} finally {
setSaving(false);
}
};
const onDragEnd = (event: DragEndEvent) => {
if (!event.over) return;
const shortcutOrder = movePreferenceId(
pinnedIds,
String(event.active.id),
String(event.over.id),
);
void commit({ ...preferences, shortcutOrder });
};
return (
<section
aria-labelledby="housekeeping-favorites-title"
className="space-y-3"
>
<h2 id="housekeeping-favorites-title" className="text-lg font-semibold">
{labels.title}
</h2>
<p id="housekeeping-favorites-reorder-instructions" className="sr-only">
{labels.reorderInstructions}
</p>
{error ? (
<p role="alert" className="text-[var(--admin-danger)]">
{labels.error}
</p>
) : null}
<DndContext sensors={sensors} onDragEnd={onDragEnd}>
<SortableContext
items={pinnedIds}
strategy={verticalListSortingStrategy}
>
<ul className="space-y-2">
{pinnedIds.map((id) => {
const route = routeById.get(id);
return route ? (
<SortableFavorite
key={id}
route={route}
title={titles[route.labelKey] ?? route.id}
unpinLabel={labels.unpin}
reorderInstructionsId="housekeeping-favorites-reorder-instructions"
unpin={() => void commit(togglePinnedRoute(preferences, id))}
/>
) : null;
})}
</ul>
</SortableContext>
</DndContext>
<ul className="space-y-2">
{unpinned.map((route) => (
<li
key={route.id}
className="flex min-w-0 items-center justify-between gap-3 p-2"
>
<span className="min-w-0 break-words">
{titles[route.labelKey] ?? route.id}
</span>
<button
type="button"
disabled={saving}
onClick={() =>
void commit(togglePinnedRoute(preferences, route.id))
}
className="rounded-md px-2 py-1 text-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
{labels.pin}
</button>
</li>
))}
</ul>
</section>
);
}
@@ -1,45 +1,33 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";
import { describe, expect, it } from "vitest";
import type { HousekeepingNavigationDomain } from "../navigation";
import { HousekeepingShell } from "./housekeeping-shell";
vi.mock("@/actions/housekeeping-search", () => ({
executeHousekeepingSearch: vi.fn(),
}));
const labels = {
skipToContent: "Skip to housekeeping content",
primaryNavigation: "Primary housekeeping navigation",
contextualNavigation: "Operations navigation",
command: "Search housekeeping",
command: "Commands are unavailable in preview",
preview: "Preview",
backToSite: "Back to site",
operatorRank: "Rank 6",
commandDeck: {
placeholder: "Search navigation, commands, and entities",
navigation: "Navigation",
commands: "Commands",
entities: "Entities",
loading: "Searching…",
empty: "No results",
partial: "Some providers are unavailable",
close: "Close",
},
};
const domains: readonly HousekeepingNavigationDomain[] = [
{
id: "operations",
href: "/ase/operations",
href: "/admin-next/operations",
iconId: "inbox",
label: "Operations",
description: "Manage operations",
items: [{ id: "queue", href: "/ase/operations/queue", label: "Queue" }],
items: [
{ id: "queue", href: "/admin-next/operations/queue", label: "Queue" },
],
},
{
id: "people",
href: "/ase/people",
href: "/admin-next/people",
iconId: "users",
label: "People",
description: "Manage people",
@@ -47,7 +35,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
},
{
id: "content",
href: "/ase/content",
href: "/admin-next/content",
iconId: "file-text",
label: "Content",
description: "Manage content",
@@ -55,7 +43,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
},
{
id: "economy",
href: "/ase/economy",
href: "/admin-next/economy",
iconId: "gem",
label: "Economy",
description: "Manage economy",
@@ -63,7 +51,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
},
{
id: "hotel",
href: "/ase/hotel",
href: "/admin-next/hotel",
iconId: "hotel",
label: "Hotel",
description: "Manage hotel",
@@ -71,7 +59,7 @@ const domains: readonly HousekeepingNavigationDomain[] = [
},
{
id: "system",
href: "/ase/system",
href: "/admin-next/system",
iconId: "settings",
label: "System",
description: "Manage system",
@@ -152,7 +140,6 @@ function renderShell(
activeDomainId={activeDomainId}
domains={availableDomains}
labels={labels}
search={vi.fn()}
>
<p>Deck body</p>
</HousekeepingShell>,
@@ -191,16 +178,16 @@ describe("HousekeepingShell", () => {
expect(html).toContain('<nav aria-label="Operations navigation"');
expect(html).toContain('<main id="housekeeping-content"');
expect(html).toContain(">Deck body</p>");
expect(html).toContain('href="/ase/operations/queue"');
expect(html).toContain('href="/admin-next/operations/queue"');
expect(html).toContain(">Queue<");
expect(html).toContain(">Nora<");
expect(html).toContain(">6<");
expect(html).not.toContain(">Preview<");
expect(html).toContain(">Preview<");
expect(html).toContain('href="/"');
expect(html).toContain(">Back to site<");
expect(html).toContain('aria-haspopup="dialog"');
expect(html).toContain(">Search housekeeping<");
expect(html).not.toContain('disabled=""');
expect(html).toMatch(
/<button type="button" disabled=""[^>]*>Commands are unavailable in preview<\/button>/,
);
});
it("maps all six authorized icons and exactly one active domain", () => {
@@ -209,7 +196,7 @@ describe("HousekeepingShell", () => {
const activeAnchors = allAnchors.filter((anchor) =>
anchor.includes('aria-current="page"'),
);
const peopleAnchor = anchorForHref(html, "/ase/people");
const peopleAnchor = anchorForHref(html, "/admin-next/people");
for (const iconClass of [
"lucide-inbox",
"lucide-users",
@@ -229,17 +216,13 @@ describe("HousekeepingShell", () => {
}
});
it("does not render a preview-only badge on the canonical surface", () => {
expect(renderShell()).not.toContain(">Preview<");
});
it("does not throw or render contextual items when a valid active domain is absent", () => {
const domainsWithoutSystem = domains.filter(
(domain) => domain.id !== "system",
);
expect(() => renderShell("system", domainsWithoutSystem)).not.toThrow();
const html = renderShell("system", domainsWithoutSystem);
expect(html).not.toContain('href="/ase/operations/queue"');
expect(html).not.toContain('href="/admin-next/operations/queue"');
expect(html).not.toContain(">Queue<");
});
@@ -3,20 +3,12 @@ import Link from "@/components/link";
import type { HousekeepingDomainId } from "../../migration/types";
import type { HousekeepingActor } from "../contracts";
import type { HousekeepingNavigationDomain } from "../navigation";
import type { HousekeepingSearchExecutor } from "./command-deck";
import { CommandTrigger } from "./command-trigger";
import { ContextNav } from "./context-nav";
import { DomainRail } from "./domain-rail";
import { OperatorSummary } from "./operator-summary";
export const HOUSEKEEPING_LANDMARKS = [
"banner",
"primary-navigation",
"context-navigation",
"main",
] as const;
export interface HousekeepingShellProps {
interface HousekeepingShellProps {
actor: HousekeepingActor;
activeDomainId: HousekeepingDomainId;
domains: readonly HousekeepingNavigationDomain[];
@@ -25,20 +17,9 @@ export interface HousekeepingShellProps {
primaryNavigation: string;
contextualNavigation: string;
command: string;
preview: string;
backToSite: string;
operatorRank: string;
commandDeck: {
placeholder: string;
navigation: string;
commands: string;
entities: string;
loading: string;
empty: string;
partial: string;
close: string;
};
};
search: HousekeepingSearchExecutor;
children: ReactNode;
}
@@ -47,68 +28,50 @@ export function HousekeepingShell({
activeDomainId,
domains,
labels,
search,
children,
}: HousekeepingShellProps) {
const activeDomain = domains.find((domain) => domain.id === activeDomainId);
return (
<div
data-housekeeping-root="true"
className="min-h-screen min-w-0 bg-[var(--admin-canvas)] text-[var(--admin-text)]"
>
<div className="min-h-screen bg-[var(--admin-canvas)] text-[var(--admin-text)]">
<a
href="#housekeeping-content"
className="sr-only rounded-md bg-[var(--admin-surface)] px-3 py-2 text-[var(--admin-text)] focus-visible:not-sr-only focus-visible:absolute focus-visible:left-4 focus-visible:top-4 focus-visible:z-10 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-4 focus:z-10 rounded-md bg-[var(--admin-surface)] px-3 py-2 text-[var(--admin-text)]"
>
{labels.skipToContent}
</a>
<header className="flex min-w-0 flex-wrap items-center justify-between gap-3 border-b border-[var(--admin-border)] bg-[var(--admin-surface)] px-4 py-3">
<div className="flex min-w-0 items-center gap-3">
<OperatorSummary actor={actor} rankLabel={labels.operatorRank} />
<header className="flex flex-wrap items-center justify-between gap-3 border-b border-[var(--admin-border)] bg-[var(--admin-surface)] px-4 py-3">
<div className="flex items-center gap-3">
<span className="rounded-full border border-[var(--admin-border)] px-2 py-1 text-xs text-[var(--admin-accent)]">
{labels.preview}
</span>
<OperatorSummary actor={actor} />
</div>
<div className="flex items-center gap-2">
<CommandTrigger
label={labels.command}
labels={labels.commandDeck}
search={search}
/>
<CommandTrigger label={labels.command} />
<Link
href="/"
className="rounded-md px-3 py-2 text-sm text-[var(--admin-text-muted)] hover:bg-[var(--admin-canvas)] hover:text-[var(--admin-text)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
className="rounded-md px-3 py-2 text-sm text-[var(--admin-text-muted)] hover:bg-[var(--admin-canvas)] hover:text-[var(--admin-text)]"
>
{labels.backToSite}
</Link>
</div>
</header>
<div
data-responsive-mode="stacked-below-lg"
className="flex min-h-[calc(100vh-4rem)] min-w-0 flex-col lg:flex-row"
>
<aside
data-navigation-collapse="domain"
className="min-w-0 border-b border-[var(--admin-border)] bg-[var(--admin-surface)] p-3 lg:border-b-0 lg:border-r"
>
<div className="flex min-h-[calc(100vh-4rem)] flex-col lg:flex-row">
<aside className="border-b border-[var(--admin-border)] bg-[var(--admin-surface)] p-3 lg:border-b-0 lg:border-r">
<DomainRail
domains={domains}
activeDomainId={activeDomainId}
ariaLabel={labels.primaryNavigation}
/>
</aside>
<div
data-navigation-collapse="context"
className="min-w-0 border-b border-[var(--admin-border)] bg-[var(--admin-surface)] p-3 lg:w-56 lg:shrink-0 lg:border-b-0 lg:border-r"
>
<div className="border-b border-[var(--admin-border)] bg-[var(--admin-surface)] p-3 lg:w-56 lg:border-b-0 lg:border-r">
<ContextNav
items={activeDomain?.items ?? []}
ariaLabel={labels.contextualNavigation}
/>
</div>
<main
id="housekeeping-content"
tabIndex={-1}
className="housekeeping-content min-w-0 max-w-full flex-1 p-4 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-[var(--admin-accent)] lg:p-6"
>
<main id="housekeeping-content" className="min-w-0 flex-1 p-4 lg:p-6">
{children}
</main>
</div>
@@ -1,117 +0,0 @@
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { anyCapability, type HousekeepingWorkItem } from "../contracts";
import {
filterOperationalInboxItems,
OperationalInbox,
projectOperationalInboxHref,
} from "./operational-inbox";
const labels = {
title: "Operational inbox",
domainFilter: "Filter by domain",
stateFilter: "Filter by state",
priorityFilter: "Filter by priority",
all: "All",
loading: "Loading operational work",
empty: "No operational work",
partial: "Some sources are unavailable",
open: "Open workflow",
};
function item(
itemId: string,
options: Partial<HousekeepingWorkItem> = {},
): HousekeepingWorkItem {
return {
sourceId: "people.queue",
itemId,
deduplicationKey: `people.queue:${itemId}`,
domain: "people",
capability: anyCapability("admin.users.view"),
severity: "warning",
priority: "high",
ageMs: 1_000,
state: "open",
occurredAt: "2026-08-30T10:00:00.000Z",
titleKey: `Item ${itemId}`,
href: "/ase/people/users",
freshness: "fresh",
actions: [],
...options,
};
}
describe("OperationalInbox", () => {
it("filters deterministically by domain, state, and priority", () => {
const items = [
item("people-open"),
item("content-draft", {
domain: "content",
state: "draft",
priority: "normal",
href: "/ase/content/editorial/articles",
}),
];
expect(
filterOperationalInboxItems(items, {
domain: "people",
state: "open",
priority: "high",
}).map((entry) => entry.itemId),
).toEqual(["people-open"]);
expect(
filterOperationalInboxItems(items, {
domain: "all",
state: "draft",
priority: "all",
}).map((entry) => entry.itemId),
).toEqual(["content-draft"]);
});
it("renders accessible filters, partial errors, and preview links", () => {
const html = renderToStaticMarkup(
<OperationalInbox
labels={labels}
response={{
items: [item("ticket-1")],
errors: [{ sourceId: "content.publication", code: "TIMEOUT" }],
correlationId: "inbox-ui",
}}
/>,
);
expect(html).toContain('aria-label="Filter by domain"');
expect(html).toContain('aria-label="Filter by state"');
expect(html).toContain('aria-label="Filter by priority"');
expect(html).toContain("Some sources are unavailable");
expect(html).not.toContain("content.publication");
expect(html).not.toContain("TIMEOUT");
expect(html).toContain('role="alert"');
expect(html).toContain('href="/ase/people/users"');
expect(html).not.toContain("Dismiss");
expect(html).not.toContain("Assign");
});
it("renders explicit loading and empty states", () => {
const response = {
items: [],
errors: [],
correlationId: "inbox-empty",
} as const;
const loading = renderToStaticMarkup(
<OperationalInbox labels={labels} response={response} loading />,
);
const empty = renderToStaticMarkup(
<OperationalInbox labels={labels} response={response} />,
);
expect(loading).toContain('role="status"');
expect(loading).toContain("Loading operational work");
expect(empty).toContain("No operational work");
});
it("projects canonical hrefs without changing production destinations", () => {
expect(projectOperationalInboxHref("/ase/system/operations/alerts")).toBe(
"/ase/system/operations/alerts",
);
});
});
@@ -1,182 +0,0 @@
"use client";
import { useMemo, useState } from "react";
import Link from "@/components/link";
import type {
CanonicalHousekeepingHref,
HousekeepingWorkItem,
} from "../contracts";
import type { HousekeepingInboxResponse } from "../inbox/inbox-service";
import { toHousekeepingHref } from "../routing/href";
export interface OperationalInboxLabels {
readonly title: string;
readonly domainFilter: string;
readonly stateFilter: string;
readonly priorityFilter: string;
readonly all: string;
readonly loading: string;
readonly empty: string;
readonly partial: string;
readonly open: string;
}
export interface OperationalInboxFilters {
readonly domain: string;
readonly state: string;
readonly priority: string;
}
export function filterOperationalInboxItems(
items: readonly HousekeepingWorkItem[],
filters: OperationalInboxFilters,
): readonly HousekeepingWorkItem[] {
return items.filter(
(item) =>
(filters.domain === "all" || item.domain === filters.domain) &&
(filters.state === "all" || item.state === filters.state) &&
(filters.priority === "all" || item.priority === filters.priority),
);
}
export function projectOperationalInboxHref(href: CanonicalHousekeepingHref) {
return toHousekeepingHref(href);
}
function unique(values: readonly string[]): readonly string[] {
return [...new Set(values)].sort((left, right) => left.localeCompare(right));
}
export function OperationalInbox({
response,
labels,
loading = false,
titles = {},
}: {
readonly response: HousekeepingInboxResponse;
readonly labels: OperationalInboxLabels;
readonly loading?: boolean;
readonly titles?: Readonly<Record<string, string>>;
}) {
const [domain, setDomain] = useState("all");
const [state, setState] = useState("all");
const [priority, setPriority] = useState("all");
const domains = useMemo(
() => unique(response.items.map((item) => item.domain)),
[response.items],
);
const states = useMemo(
() => unique(response.items.map((item) => item.state)),
[response.items],
);
const priorities = useMemo(
() => unique(response.items.map((item) => item.priority)),
[response.items],
);
const items = useMemo(
() =>
filterOperationalInboxItems(response.items, {
domain,
state,
priority,
}),
[domain, priority, response.items, state],
);
return (
<section
aria-labelledby="operational-inbox-title"
className="min-w-0 space-y-4"
>
<h2 id="operational-inbox-title" className="text-lg font-semibold">
{labels.title}
</h2>
{loading ? <p role="status">{labels.loading}</p> : null}
{!loading ? (
<>
<div className="grid gap-3 sm:grid-cols-3">
<select
aria-label={labels.domainFilter}
value={domain}
onChange={(event) => setDomain(event.target.value)}
className="min-w-0 rounded-md border border-[var(--admin-border)] bg-[var(--admin-surface)] px-3 py-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
<option value="all">{labels.all}</option>
{domains.map((value) => (
<option key={value} value={value}>
{value}
</option>
))}
</select>
<select
aria-label={labels.stateFilter}
value={state}
onChange={(event) => setState(event.target.value)}
className="min-w-0 rounded-md border border-[var(--admin-border)] bg-[var(--admin-surface)] px-3 py-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
<option value="all">{labels.all}</option>
{states.map((value) => (
<option key={value} value={value}>
{value}
</option>
))}
</select>
<select
aria-label={labels.priorityFilter}
value={priority}
onChange={(event) => setPriority(event.target.value)}
className="min-w-0 rounded-md border border-[var(--admin-border)] bg-[var(--admin-surface)] px-3 py-2 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
<option value="all">{labels.all}</option>
{priorities.map((value) => (
<option key={value} value={value}>
{value}
</option>
))}
</select>
</div>
{response.errors.length > 0 ? (
<div
role="alert"
aria-live="assertive"
className="rounded-md border border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)] p-3"
>
<p className="text-[var(--admin-warning)]">{labels.partial}</p>
</div>
) : null}
{items.length === 0 ? <p>{labels.empty}</p> : null}
<ul className="space-y-3">
{items.map((item) => (
<li
key={`${item.sourceId}:${item.itemId}`}
className="min-w-0 rounded-md border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4"
>
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0 break-words">
<p className="font-medium">
{titles[item.titleKey] ?? item.titleKey}
</p>
<p className="text-sm text-[var(--admin-text-muted)]">
{item.domain} · {item.state} · {item.priority}
</p>
<time dateTime={item.occurredAt} className="text-xs">
{item.occurredAt}
</time>
</div>
<Link
href={projectOperationalInboxHref(item.href)}
className="rounded-md border border-[var(--admin-border)] px-3 py-2 text-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
{labels.open}
</Link>
</div>
</li>
))}
</ul>
</>
) : null}
</section>
);
}
@@ -2,10 +2,9 @@ import type { HousekeepingActor } from "../contracts";
interface OperatorSummaryProps {
actor: HousekeepingActor;
rankLabel: string;
}
export function OperatorSummary({ actor, rankLabel }: OperatorSummaryProps) {
export function OperatorSummary({ actor }: OperatorSummaryProps) {
return (
<div className="min-w-0 text-sm text-[var(--admin-text)]">
<div className="truncate font-medium">{actor.username}</div>
@@ -13,8 +12,7 @@ export function OperatorSummary({ actor, rankLabel }: OperatorSummaryProps) {
data-operator-rank={actor.rank}
className="text-[var(--admin-text-muted)]"
>
<span className="sr-only">{rankLabel}</span>
<span aria-hidden="true">{actor.rank}</span>
{actor.rank}
</div>
</div>
);
@@ -1,212 +0,0 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it, vi } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import {
anyCapability,
fail,
type HousekeepingCapabilityContext,
ok,
} from "../contracts";
import { defaultHousekeepingPreferences } from "../preferences/schema";
import {
createHousekeepingRegistry,
loadHousekeepingWidgets,
} from "../registry";
import { Favorites, movePreferenceId, togglePinnedRoute } from "./favorites";
import { RecentWork } from "./recent-work";
import { WidgetGrid } from "./widget-grid";
import {
preferencesAfterSave,
toggleOptionalWidget,
WidgetSettings,
} from "./widget-settings";
const usersView = anyCapability(PERMS.USERS_VIEW);
const context: HousekeepingCapabilityContext = {
actor: { id: 42, username: "operator", rank: 7 },
isSuperAdmin: false,
has: (slug) => slug === PERMS.USERS_VIEW,
hasAny: (...slugs) => slugs.includes(PERMS.USERS_VIEW),
hasAll: (...slugs) => slugs.every((slug) => slug === PERMS.USERS_VIEW),
};
const registry = createHousekeepingRegistry([
{
id: "people",
labelKey: "people",
descriptionKey: "people.description",
iconId: "users",
canonicalHref: "/ase/people",
capability: usersView,
routes: [
{
id: "people.users",
labelKey: "Users",
href: "/ase/people/users",
capability: usersView,
},
{
id: "people.tickets",
labelKey: "Tickets",
href: "/ase/people/tickets",
capability: usersView,
},
],
searchProviders: [],
inboxSources: [],
widgets: [
{
id: "people.mandatory",
owner: "people",
capability: usersView,
kind: "mandatory",
load: async () => ok({ count: 1 }, "mandatory"),
},
{
id: "people.optional",
owner: "people",
capability: usersView,
kind: "optional",
load: async () => ok({ count: 2 }, "optional"),
},
{
id: "people.failing",
owner: "people",
capability: usersView,
kind: "optional",
load: async () => fail("TIMEOUT", "timeout", "failing"),
},
],
},
]);
const initialPreferences = {
...defaultHousekeepingPreferences(),
pinnedRouteIds: ["people.users"],
shortcutOrder: ["people.users"],
widgetOrder: ["stale.widget", "people.optional", "people.mandatory"],
enabledOptionalWidgetIds: ["people.optional", "people.failing"],
};
const save = vi.fn(async (value) => ok(value, "saved"));
describe("housekeeping personalization", () => {
it("loads enabled widgets independently in reconciled order and reports partial failures", async () => {
const result = await loadHousekeepingWidgets(
registry,
initialPreferences,
context,
);
expect(result.widgets).toEqual([
{ id: "people.optional", data: { count: 2 } },
{ id: "people.mandatory", data: { count: 1 } },
]);
expect(result.errors).toEqual([
{ widgetId: "people.failing", code: "TIMEOUT" },
]);
});
it("pins, unpins, and reorders only stable preference identifiers", () => {
const pinned = togglePinnedRoute(initialPreferences, "people.tickets");
expect(pinned.pinnedRouteIds).toEqual(["people.users", "people.tickets"]);
expect(pinned.shortcutOrder).toEqual(["people.users", "people.tickets"]);
expect(
movePreferenceId(pinned.shortcutOrder, "people.tickets", "people.users"),
).toEqual(["people.tickets", "people.users"]);
expect(togglePinnedRoute(pinned, "people.users")).toMatchObject({
pinnedRouteIds: ["people.tickets"],
shortcutOrder: ["people.tickets"],
});
});
it("locks mandatory widgets, toggles optional widgets, and retains UI state after a failed save", () => {
const enabled = toggleOptionalWidget(initialPreferences, "people.optional");
expect(enabled.enabledOptionalWidgetIds).toEqual(["people.failing"]);
const failure = fail(
"INTERNAL",
"errors.housekeeping.preferences.save",
"save-failed",
);
expect(preferencesAfterSave(initialPreferences, enabled, failure)).toEqual({
preferences: initialPreferences,
error: "errors.housekeeping.preferences.save",
});
});
it("renders favorites, recent links, locked settings, keyboard ordering, and widget errors accessibly", () => {
const widgets = {
widgets: [
{ id: "people.optional", data: { count: 2 } },
{ id: "people.mandatory", data: { count: 1 } },
],
errors: [{ widgetId: "people.failing", code: "TIMEOUT" as const }],
};
const routes = registry.domains[0]?.routes ?? [];
const definitions = registry.domains[0]?.widgets ?? [];
const html = renderToStaticMarkup(
<>
<Favorites
preferences={initialPreferences}
routes={routes}
savePreferences={save}
labels={{
title: "Favorites",
pin: "Pin",
unpin: "Unpin",
error: "Save failed",
reorderInstructions: "Press Space, then use arrow keys.",
}}
/>
<RecentWork
items={[
{
routeId: "people.users",
canonicalHref: "/ase/people/users",
labelKey: "Users",
occurredAt: "2026-08-30T10:00:00.000Z",
source: "route-visit",
},
]}
labels={{ title: "Recent work", empty: "Nothing recent" }}
/>
<WidgetSettings
preferences={initialPreferences}
widgets={definitions}
savePreferences={save}
labels={{
title: "Widget settings",
mandatory: "Required",
error: "Save failed",
}}
/>
<WidgetGrid
preferences={initialPreferences}
result={widgets}
savePreferences={save}
labels={{
title: "Widgets",
partial: "Some widgets failed",
error: "Save failed",
reorderInstructions: "Press Space, then use arrow keys.",
}}
/>
</>,
);
expect(html).toContain('href="/ase/people/users"');
expect(html).toContain("Required");
expect(html).toContain('disabled=""');
expect(html).toContain("Some widgets failed");
expect(html).not.toContain("TIMEOUT");
const gridSource = readFileSync(
resolve(
process.cwd(),
"src/features/housekeeping/foundation/shell/widget-grid.tsx",
),
"utf8",
);
expect(gridSource).toContain("KeyboardSensor");
expect(gridSource).toContain("sortableKeyboardCoordinates");
});
});
@@ -1,50 +0,0 @@
import Link from "@/components/link";
import type { HousekeepingRecentItem } from "../recent/recent-work";
import { toHousekeepingHref } from "../routing/href";
export interface RecentWorkLabels {
readonly title: string;
readonly empty: string;
}
export function RecentWork({
items,
labels,
titles = {},
}: {
readonly items: readonly HousekeepingRecentItem[];
readonly labels: RecentWorkLabels;
readonly titles?: Readonly<Record<string, string>>;
}) {
return (
<section
aria-labelledby="housekeeping-recent-title"
className="min-w-0 space-y-3"
>
<h2 id="housekeeping-recent-title" className="text-lg font-semibold">
{labels.title}
</h2>
{items.length === 0 ? <p>{labels.empty}</p> : null}
<ul className="space-y-2">
{items.map((item) => (
<li
key={item.routeId}
className="min-w-0 rounded-md border border-[var(--admin-border)] bg-[var(--admin-surface)] p-3"
>
<Link
href={toHousekeepingHref(item.canonicalHref)}
className="break-words rounded focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
{titles[item.labelKey] ?? item.routeId}
</Link>
<div className="text-xs text-[var(--admin-text-muted)]">
<span>{item.source}</span>
{" · "}
<time dateTime={item.occurredAt}>{item.occurredAt}</time>
</div>
</li>
))}
</ul>
</section>
);
}
@@ -1,170 +0,0 @@
"use client";
import {
DndContext,
type DragEndEvent,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
} from "@dnd-kit/core";
import {
SortableContext,
sortableKeyboardCoordinates,
useSortable,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { useState } from "react";
import type { HousekeepingPreferences } from "../preferences/schema";
import type { HousekeepingWidgetLoadResult } from "../registry";
import { movePreferenceId } from "./favorites";
import {
type HousekeepingPreferenceSaver,
preferencesAfterSave,
} from "./widget-settings";
function SortableWidget({
id,
title,
data,
reorderInstructionsId,
}: {
readonly id: string;
readonly title: string;
readonly data: unknown;
readonly reorderInstructionsId: string;
}) {
const { attributes, listeners, setNodeRef, transform, transition } =
useSortable({ id });
return (
<li
ref={setNodeRef}
style={{ transform: CSS.Transform.toString(transform), transition }}
className="min-w-0 rounded-md border border-[var(--admin-border)] bg-[var(--admin-surface)] p-4 motion-reduce:transition-none"
>
<div className="flex items-center gap-2">
<button
type="button"
{...attributes}
{...listeners}
aria-label={title}
aria-describedby={reorderInstructionsId}
className="cursor-grab rounded text-[var(--admin-text-muted)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
>
↕
</button>
<h3 className="min-w-0 break-words font-medium">{title}</h3>
</div>
<pre className="mt-2 max-w-full overflow-auto text-xs">
{JSON.stringify(data, null, 2)}
</pre>
</li>
);
}
export interface WidgetGridLabels {
readonly title: string;
readonly partial: string;
readonly error: string;
readonly reorderInstructions: string;
}
export function WidgetGrid({
preferences: initialPreferences,
result,
savePreferences,
labels,
titles = {},
}: {
readonly preferences: HousekeepingPreferences;
readonly result: HousekeepingWidgetLoadResult;
readonly savePreferences: HousekeepingPreferenceSaver;
readonly labels: WidgetGridLabels;
readonly titles?: Readonly<Record<string, string>>;
}) {
const [preferences, setPreferences] = useState(initialPreferences);
const [error, setError] = useState(false);
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
);
const dataById = new Map(
result.widgets.map((widget) => [widget.id, widget.data]),
);
const ids = preferences.widgetOrder.filter((id) => dataById.has(id));
for (const widget of result.widgets) {
if (!ids.includes(widget.id)) ids.push(widget.id);
}
const onDragEnd = async (event: DragEndEvent) => {
if (!event.over) return;
const moved = movePreferenceId(
ids,
String(event.active.id),
String(event.over.id),
);
const retained = preferences.widgetOrder.filter((id) => !ids.includes(id));
const proposed = { ...preferences, widgetOrder: [...moved, ...retained] };
setError(false);
try {
const outcome = preferencesAfterSave(
preferences,
proposed,
await savePreferences(proposed),
);
setPreferences(outcome.preferences);
setError(outcome.error !== undefined);
} catch {
setError(true);
}
};
return (
<section
aria-labelledby="housekeeping-widget-grid-title"
className="space-y-3"
>
<h2 id="housekeeping-widget-grid-title" className="text-lg font-semibold">
{labels.title}
</h2>
<p id="housekeeping-widgets-reorder-instructions" className="sr-only">
{labels.reorderInstructions}
</p>
{error ? (
<p role="alert" className="text-[var(--admin-danger)]">
{labels.error}
</p>
) : null}
{result.errors.length > 0 ? (
<div
role="alert"
aria-live="assertive"
className="rounded-md border border-[var(--admin-warning-border)] bg-[var(--admin-warning-subtle)] p-3"
>
<p className="text-[var(--admin-warning)]">{labels.partial}</p>
</div>
) : null}
<DndContext
sensors={sensors}
onDragEnd={(event) => void onDragEnd(event)}
>
<SortableContext items={ids} strategy={verticalListSortingStrategy}>
<ul className="grid gap-3 md:grid-cols-2">
{ids.map((id) => (
<SortableWidget
key={id}
id={id}
title={titles[id] ?? id}
data={dataById.get(id)}
reorderInstructionsId="housekeeping-widgets-reorder-instructions"
/>
))}
</ul>
</SortableContext>
</DndContext>
</section>
);
}
@@ -1,135 +0,0 @@
"use client";
import { useState } from "react";
import type { HousekeepingResult } from "../contracts";
import type { HousekeepingPreferences } from "../preferences/schema";
import type { HousekeepingWidgetOption } from "../preferences/widget-options";
export type HousekeepingPreferenceSaver = (
preferences: HousekeepingPreferences,
) => Promise<HousekeepingResult<HousekeepingPreferences>>;
export function toggleOptionalWidget(
preferences: HousekeepingPreferences,
widgetId: string,
): HousekeepingPreferences {
const enabled = preferences.enabledOptionalWidgetIds.includes(widgetId);
return {
...preferences,
widgetOrder:
!enabled && !preferences.widgetOrder.includes(widgetId)
? [...preferences.widgetOrder, widgetId]
: [...preferences.widgetOrder],
enabledOptionalWidgetIds: enabled
? preferences.enabledOptionalWidgetIds.filter((id) => id !== widgetId)
: [...preferences.enabledOptionalWidgetIds, widgetId],
};
}
export function preferencesAfterSave(
current: HousekeepingPreferences,
_proposed: HousekeepingPreferences,
result: HousekeepingResult<HousekeepingPreferences>,
): { preferences: HousekeepingPreferences; error?: string } {
return result.ok
? { preferences: result.data }
: { preferences: current, error: result.error.messageKey };
}
export interface WidgetSettingsLabels {
readonly title: string;
readonly mandatory: string;
readonly error: string;
}
export function WidgetSettings({
preferences: initialPreferences,
widgets,
savePreferences,
labels,
titles = {},
}: {
readonly preferences: HousekeepingPreferences;
readonly widgets: readonly HousekeepingWidgetOption[];
readonly savePreferences: HousekeepingPreferenceSaver;
readonly labels: WidgetSettingsLabels;
readonly titles?: Readonly<Record<string, string>>;
}) {
const [preferences, setPreferences] = useState(initialPreferences);
const [savingId, setSavingId] = useState<string | null>(null);
const [error, setError] = useState<string | null>(null);
const toggle = async (widgetId: string) => {
const proposed = toggleOptionalWidget(preferences, widgetId);
setSavingId(widgetId);
setError(null);
try {
const outcome = preferencesAfterSave(
preferences,
proposed,
await savePreferences(proposed),
);
setPreferences(outcome.preferences);
setError(outcome.error ?? null);
} catch {
setError(labels.error);
} finally {
setSavingId(null);
}
};
return (
<section
aria-labelledby="housekeeping-widget-settings-title"
className="space-y-3"
>
<h2
id="housekeeping-widget-settings-title"
className="text-lg font-semibold"
>
{labels.title}
</h2>
{error ? (
<p
role="alert"
aria-live="assertive"
className="text-[var(--admin-danger)]"
>
{error === labels.error ? error : labels.error}
</p>
) : null}
<ul className="space-y-2">
{widgets.map((widget) => {
const mandatory = widget.kind === "mandatory";
const checked =
mandatory ||
preferences.enabledOptionalWidgetIds.includes(widget.id);
return (
<li
key={widget.id}
className="flex min-w-0 items-center justify-between gap-3 rounded-md border border-[var(--admin-border)] p-3"
>
<label className="flex min-w-0 items-center gap-2">
<input
type="checkbox"
checked={checked}
disabled={mandatory || savingId !== null}
onChange={() => void toggle(widget.id)}
className="focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--admin-accent)]"
/>
<span className="min-w-0 break-words">
{titles[widget.id] ?? widget.id}
</span>
</label>
{mandatory ? (
<span className="text-xs text-[var(--admin-text-muted)]">
{labels.mandatory}
</span>
) : null}
</li>
);
})}
</ul>
</section>
);
}