docs: complete housekeeping migration matrix

This commit is contained in:
Simo committed 2026-08-25 18:49:58 +02:00
1 parent 9b7d5de12f
commit 3b3d7780c9
6 files changed
+634 -1

No files matched your search

+2 -1
View File
@@ -21,7 +21,8 @@
"db:generate": "drizzle-kit generate",
"db:migrate": "tsx scripts/apply-migrations.ts",
"db:migrate:status": "tsx scripts/apply-migrations.ts --status",
"db:studio": "drizzle-kit studio"
"db:studio": "drizzle-kit studio",
"hk:matrix:check": "tsx scripts/verify-housekeeping-matrix.ts"
},
"lint-staged": {
"*.{js,ts,jsx,tsx,json}": "biome check --write --no-errors-on-unmatched"
+18
View File
@@ -0,0 +1,18 @@
import { discoverLegacyPages } from "../src/features/housekeeping/migration/discover-legacy-pages";
import { HOUSEKEEPING_MIGRATION_MATRIX } from "../src/features/housekeeping/migration/matrix";
import { validateMigrationEntries } from "../src/features/housekeeping/migration/validate-matrix";
const discovered = discoverLegacyPages();
const issues = validateMigrationEntries(
discovered,
HOUSEKEEPING_MIGRATION_MATRIX,
);
if (issues.length > 0) {
for (const issue of issues) console.error(issue);
process.exitCode = 1;
} else {
console.log(
`Housekeeping migration matrix: ${HOUSEKEEPING_MIGRATION_MATRIX.length}/${discovered.length} valid`,
);
}
@@ -0,0 +1,29 @@
import { describe, expect, it } from "vitest";
import { discoverLegacyPages } from "./discover-legacy-pages";
import { HOUSEKEEPING_MIGRATION_MATRIX } from "./matrix";
import { validateMigrationEntries } from "./validate-matrix";
describe("HOUSEKEEPING_MIGRATION_MATRIX", () => {
it("covers all 137 legacy pages exactly once", () => {
const discovered = discoverLegacyPages();
expect(HOUSEKEEPING_MIGRATION_MATRIX).toHaveLength(137);
expect(
validateMigrationEntries(discovered, HOUSEKEEPING_MIGRATION_MATRIX),
).toEqual([]);
});
it("contains no undecided evidence markers", () => {
expect(JSON.stringify(HOUSEKEEPING_MIGRATION_MATRIX)).not.toMatch(
/TBD|TODO|FIXME|UNCLASSIFIED/,
);
});
it("is sorted by legacy path", () => {
expect(HOUSEKEEPING_MIGRATION_MATRIX.map((row) => row.legacyPath)).toEqual(
[...HOUSEKEEPING_MIGRATION_MATRIX.map((row) => row.legacyPath)].sort(
(a, b) => a.localeCompare(b),
),
);
});
});
@@ -0,0 +1,15 @@
import { contentMigrationEntries } from "./content";
import { economyMigrationEntries } from "./economy";
import { hotelMigrationEntries } from "./hotel";
import { operationsMigrationEntries } from "./operations";
import { peopleMigrationEntries } from "./people";
import { systemMigrationEntries } from "./system";
export const HOUSEKEEPING_MIGRATION_MATRIX = [
...operationsMigrationEntries,
...peopleMigrationEntries,
...contentMigrationEntries,
...economyMigrationEntries,
...hotelMigrationEntries,
...systemMigrationEntries,
].sort((a, b) => a.legacyPath.localeCompare(b.legacyPath));
@@ -0,0 +1,128 @@
import { describe, expect, it } from "vitest";
import { PERMS } from "@/lib/permission-slugs";
import { ownedLegacyPages } from "./discover-legacy-pages";
import { systemMigrationEntries } from "./system";
import { validateMigrationEntries } from "./validate-matrix";
const SYSTEM_PREFIXES = [
"/admin/alerts",
"/admin/analytics",
"/admin/commandocentrum",
"/admin/devops",
"/admin/emulator",
"/admin/housekeeping",
"/admin/logs",
"/admin/maintenance",
"/admin/menu",
"/admin/permissions",
"/admin/settings",
] as const;
describe("systemMigrationEntries", () => {
it("covers all 19 System pages exactly once", () => {
expect(systemMigrationEntries).toHaveLength(19);
expect(
validateMigrationEntries(
ownedLegacyPages(SYSTEM_PREFIXES),
systemMigrationEntries,
),
).toEqual([]);
});
it("removes legacy foundation-owned pages", () => {
expect(systemMigrationEntries).toEqual(
expect.arrayContaining([
expect.objectContaining({
legacyPath: "/admin/housekeeping",
decision: "REMOVE",
targetPath: null,
}),
expect.objectContaining({
legacyPath: "/admin/menu",
decision: "REMOVE",
targetPath: null,
}),
]),
);
});
it("keeps live permissions as a System workflow", () => {
expect(systemMigrationEntries).toContainEqual(
expect.objectContaining({
legacyPath: "/admin/permissions",
targetPath: "/admin/system/access/permissions",
decision: expect.not.stringMatching("REMOVE"),
}),
);
});
it("retains the read-only legacy housekeeping value in removal notes", () => {
const housekeeping = systemMigrationEntries.find(
(row) => row.legacyPath === "/admin/housekeeping",
);
expect(housekeeping?.dependencies.queries).toEqual(
expect.arrayContaining([
"exportPermissions",
"getAuditLogs",
"legacy ACL comparison",
]),
);
expect(housekeeping?.notes.join(" ")).toMatch(
/read-only.*export.*comparison/i,
);
});
it("uses only the approved System target roots", () => {
const allowedRoots = [
"/admin/system/access",
"/admin/system/configuration",
"/admin/system/observability",
"/admin/system/operations",
];
expect(
systemMigrationEntries.every(
(row) =>
row.targetPath === null ||
allowedRoots.some(
(root) =>
row.targetPath === root || row.targetPath?.startsWith(`${root}/`),
),
),
).toBe(true);
});
it("keeps non-removal workflows planned without parity claims", () => {
expect(
systemMigrationEntries
.filter((row) => row.decision !== "REMOVE")
.every(
(row) => row.status === "PLANNED" && row.parityEvidence.length === 0,
),
).toBe(true);
});
it("records authoritative capabilities for privileged System mutations", () => {
expect(systemMigrationEntries).toEqual(
expect.arrayContaining([
expect.objectContaining({
legacyPath: "/admin/commandocentrum",
capabilities: {
read: [PERMS.RCON_EXECUTE],
mutate: [PERMS.RCON_EXECUTE],
},
auditRequirement: "PRIVILEGED_MUTATION",
}),
expect.objectContaining({
legacyPath: "/admin/permissions/:id",
capabilities: {
read: [PERMS.PERMISSIONS_MANAGE],
mutate: [PERMS.PERMISSIONS_MANAGE],
},
auditRequirement: "PRIVILEGED_MUTATION",
}),
]),
);
});
});
@@ -0,0 +1,442 @@
import { PERMS } from "@/lib/permission-slugs";
import type { MigrationEntry } from "./types";
type PlannedSystemEntry = Omit<
MigrationEntry,
"targetDomain" | "requiredTests" | "parityEvidence" | "status" | "notes"
> & {
requiredTests?: MigrationEntry["requiredTests"];
notes?: MigrationEntry["notes"];
};
function plannedSystemEntry(entry: PlannedSystemEntry): MigrationEntry {
return {
...entry,
targetDomain: "system",
requiredTests: entry.requiredTests ?? ["integration", "e2e", "visual"],
parityEvidence: [],
status: "PLANNED",
notes: entry.notes ?? [],
};
}
export const systemMigrationEntries: readonly MigrationEntry[] = [
// Operator alerts and read-only analytics.
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/alerts",
sourceFile: "src/app/admin/alerts/page.tsx",
targetPath: "/admin/system/operations/alerts",
decision: "REBUILD",
capabilities: {
read: [PERMS.NOTIFICATIONS_VIEW],
mutate: [PERMS.NOTIFICATIONS_EDIT, PERMS.NOTIFICATIONS_VIEW],
},
dependencies: {
queries: ["AlertLogs"],
mutations: [
"markAllAlertsRead",
"rcon.send hotelalert",
"sendHotelAlert",
],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: [
"Marking alerts read and broadcasting a hotel alert use different current permission slugs",
],
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/analytics",
sourceFile: "src/app/admin/analytics/page.tsx",
targetPath: "/admin/system/observability/analytics",
decision: "REBUILD",
capabilities: { read: [PERMS.ANALYTICS_VIEW], mutate: [] },
dependencies: {
queries: [
"Ban",
"Rooms",
"RoomTradeLog",
"User",
"chatlogs_room raw SQL",
"redisCache",
],
mutations: [],
},
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/analytics/activity",
sourceFile: "src/app/admin/analytics/activity/page.tsx",
targetPath: "/admin/system/observability/analytics/activity",
decision: "REBUILD",
capabilities: { read: [PERMS.ANALYTICS_VIEW], mutate: [] },
dependencies: {
queries: [
"Ban",
"User",
"bans raw SQL",
"chatlogs_room raw SQL",
"commandlogs raw SQL",
"redisCache",
"users raw SQL",
],
mutations: [],
},
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/analytics/economy",
sourceFile: "src/app/admin/analytics/economy/page.tsx",
targetPath: "/admin/system/observability/analytics/economy",
decision: "REBUILD",
capabilities: { read: [PERMS.ANALYTICS_VIEW], mutate: [] },
dependencies: {
queries: [
"LogsShopPurchases",
"MarketplaceItems",
"RoomTradeLog",
"User",
"logs_shop_purchases raw SQL",
"redisCache",
],
mutations: [],
},
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
}),
// Runtime operations, health, and emulator configuration.
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/commandocentrum",
sourceFile: "src/app/admin/commandocentrum/page.tsx",
targetPath: "/admin/system/operations/command-center",
decision: "REBUILD",
capabilities: {
read: [PERMS.RCON_EXECUTE],
mutate: [PERMS.RCON_EXECUTE],
},
dependencies: {
queries: [
"EmulatorErrors",
"StaffActivities",
"User",
"fetchOpsHealth",
"fetchOpsOnlineUsers",
"siteSettings.getBool",
],
mutations: [
"alertUser",
"disconnectUser",
"executeCommand",
"forwardUser",
"giveBadge",
"giveCredits",
"giveDiamonds",
"giveDuckets",
"hotelAlert",
"sendGift",
"setMotto",
"setRank",
"updateCatalog",
"updateNavigator",
"updateWordFilter",
],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: [
"The current set-rank action combines RCON with a database update and rank-relative authorization",
],
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/devops",
sourceFile: "src/app/admin/devops/page.tsx",
targetPath: "/admin/system/observability/devops",
decision: "REBUILD",
capabilities: { read: [PERMS.DEVOPS_VIEW], mutate: [] },
dependencies: {
queries: [
"EmulatorErrors",
"EmulatorSettings",
"GET /api/admin/devops/health",
"fetchOpsHealth",
],
mutations: [],
},
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: ["Health checks probe the database, Redis, RCON, and online users"],
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/devops/errors",
sourceFile: "src/app/admin/devops/errors/page.tsx",
targetPath: "/admin/system/observability/devops/errors",
decision: "REBUILD",
capabilities: { read: [PERMS.DEVOPS_VIEW], mutate: [] },
dependencies: { queries: ["EmulatorErrors"], mutations: [] },
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/emulator",
sourceFile: "src/app/admin/emulator/page.tsx",
targetPath: "/admin/system/configuration/emulator",
decision: "REBUILD",
capabilities: {
read: [PERMS.SETTINGS_VIEW],
mutate: [PERMS.SETTINGS_EDIT],
},
dependencies: {
queries: ["EmulatorSettings", "EmulatorTexts"],
mutations: ["updateEmulatorSetting", "updateEmulatorText"],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: ["The current actions upsert emulator-owned settings and texts"],
}),
// Foundation-owned legacy pages are retired, not migrated as workflows.
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/housekeeping",
sourceFile: "src/app/admin/housekeeping/page.tsx",
targetPath: null,
decision: "REMOVE",
capabilities: { read: [PERMS.SETTINGS_VIEW], mutate: [] },
dependencies: {
queries: [
"AclModelPermission",
"AclPermission",
"AclRole",
"WebsiteHousekeepingPermissions",
"exportPermissions",
"getAuditLogs",
"legacy ACL comparison",
],
mutations: [],
},
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
requiredTests: ["integration", "e2e"],
notes: [
"Remove the legacy foundation surface while preserving its read-only export and ACL comparison value in the live access workflow",
"Legacy housekeeping writes already redirect to live Permissions",
],
}),
// Staff, audit, chat, command, and trade observability.
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/logs",
sourceFile: "src/app/admin/logs/page.tsx",
targetPath: "/admin/system/observability/logs/staff",
decision: "REBUILD",
capabilities: { read: [PERMS.LOGS_VIEW], mutate: [] },
dependencies: { queries: ["StaffActivities", "User"], mutations: [] },
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/logs/audit",
sourceFile: "src/app/admin/logs/audit/page.tsx",
targetPath: "/admin/system/observability/logs/audit",
decision: "REBUILD",
capabilities: { read: [PERMS.LOGS_VIEW], mutate: [] },
dependencies: { queries: ["getAuditLogs"], mutations: [] },
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/logs/chat",
sourceFile: "src/app/admin/logs/chat/page.tsx",
targetPath: "/admin/system/observability/logs/chat",
decision: "REBUILD",
capabilities: { read: [PERMS.LOGS_VIEW], mutate: [] },
dependencies: {
queries: ["chatlogs_room raw SQL", "loadChatLogList", "users raw SQL"],
mutations: [],
},
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/logs/commands",
sourceFile: "src/app/admin/logs/commands/page.tsx",
targetPath: "/admin/system/observability/logs/commands",
decision: "REBUILD",
capabilities: { read: [PERMS.LOGS_VIEW], mutate: [] },
dependencies: {
queries: ["commandlogs raw SQL", "loadCommandLogList", "users raw SQL"],
mutations: [],
},
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/logs/trades",
sourceFile: "src/app/admin/logs/trades/page.tsx",
targetPath: "/admin/system/observability/logs/trades",
decision: "REBUILD",
capabilities: { read: [PERMS.LOGS_VIEW], mutate: [] },
dependencies: {
queries: ["loadTradeLogList", "room_trade_log raw SQL", "users raw SQL"],
mutations: [],
},
auditRequirement: "NONE",
localization: "PARTIAL",
accessibility: "PARTIAL",
}),
// Global configuration and access control.
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/maintenance",
sourceFile: "src/app/admin/maintenance/page.tsx",
targetPath: "/admin/system/operations/maintenance",
decision: "REBUILD",
capabilities: {
read: [PERMS.SETTINGS_VIEW],
mutate: [PERMS.SETTINGS_EDIT],
},
dependencies: {
queries: ["WebsiteSetting"],
mutations: ["saveMaintenance", "siteSettings.reload"],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: ["Maintenance changes global login availability and splash content"],
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/menu",
sourceFile: "src/app/admin/menu/page.tsx",
targetPath: null,
decision: "REMOVE",
capabilities: {
read: [PERMS.SETTINGS_VIEW],
mutate: [PERMS.SETTINGS_EDIT],
},
dependencies: {
queries: ["ADMIN_NAV_GROUPS", "siteSettings.get"],
mutations: ["saveAdminNavConfig", "siteSettings.update"],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
requiredTests: ["integration", "e2e"],
notes: [
"Remove the manual legacy menu editor because foundation manifests own navigation",
],
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/permissions",
sourceFile: "src/app/admin/permissions/page.tsx",
targetPath: "/admin/system/access/permissions",
decision: "REBUILD",
capabilities: {
read: [PERMS.PERMISSIONS_MANAGE],
mutate: [PERMS.PERMISSIONS_MANAGE],
},
dependencies: {
queries: ["fetchEmulatorRankSummaries", "users grouped by rank"],
mutations: [
"createRank",
"deleteRank",
"repairAdminNavAclGrants",
"rcon.send updatepermissions",
],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: ["Keep this live ACL and emulator-rank workflow in System access"],
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/permissions/:id",
sourceFile: "src/app/admin/permissions/[id]/page.tsx",
targetPath: "/admin/system/access/permissions/:id",
decision: "REBUILD",
capabilities: {
read: [PERMS.PERMISSIONS_MANAGE],
mutate: [PERMS.PERMISSIONS_MANAGE],
},
dependencies: {
queries: [
"AclModelPermission",
"AclPermission",
"AclRole",
"User",
"fetchEmulatorRankForEdit",
],
mutations: [
"AclRole load-time synchronization",
"rcon.send updatepermissions",
"saveRank",
"setCmsPermissions",
],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: [
"The current route can create or retitle a CMS role while loading rank detail",
],
}),
plannedSystemEntry({
surface: "admin",
legacyPath: "/admin/settings",
sourceFile: "src/app/admin/settings/page.tsx",
targetPath: "/admin/system/configuration/settings",
decision: "REBUILD",
capabilities: {
read: [PERMS.SETTINGS_VIEW],
mutate: [PERMS.SETTINGS_EDIT],
},
dependencies: {
queries: ["WebsiteSetting"],
mutations: [
"createSetting",
"deleteSetting",
"saveManagedSettings",
"siteSettings.reload",
"updateSetting",
],
},
auditRequirement: "PRIVILEGED_MUTATION",
localization: "PARTIAL",
accessibility: "PARTIAL",
notes: [
"Global setting changes can also invalidate official gamedata and badge caches",
],
}),
];