docs: define complete housekeeping cutover
This commit is contained in:
1 parent
d5eadeb3a7
commit
d42cd2af53
1 file changed
+411
@@ -0,0 +1,411 @@
|
||||
# Housekeeping Completion and Atomic Cutover Design
|
||||
|
||||
**Status:** Approved in conversation on 2026-08-26
|
||||
**Delivery branch:** `codex/housekeeping-complete`
|
||||
**Delivery shape:** one final pull request
|
||||
**Cutover:** atomic, with no compatibility redirects
|
||||
|
||||
## Relationship to the existing design
|
||||
|
||||
This specification completes the program described by
|
||||
`2026-08-24-housekeeping-modernization-design.md` after the merged Inventory &
|
||||
Foundation subproject. The existing foundation is not the finished product: it
|
||||
provides the 137-route migration matrix, capability-aware contracts, validated
|
||||
domain manifests, shell primitives, and a non-production preview.
|
||||
|
||||
This document defines the remaining implementation and the final cutover. Where
|
||||
delivery details differ, this document is authoritative for phases 02 onward.
|
||||
The master architecture remains authoritative for domain ownership and product
|
||||
behavior.
|
||||
|
||||
## Approved decisions
|
||||
|
||||
- Build complete verticals behind the existing non-production gate.
|
||||
- Keep all remaining work on one branch and deliver it through one final pull
|
||||
request.
|
||||
- Implement in vertical slices rather than UI-first placeholders.
|
||||
- Keep current `/admin` and `/mod` behavior unchanged until the final cutover
|
||||
commit.
|
||||
- At cutover, make the new Command Deck the real `/admin`, remove `/mod`, and
|
||||
remove obsolete legacy routes without redirects.
|
||||
- Use hybrid personalization: mandatory content is capability-derived; operators
|
||||
may pin and reorder allowed shortcuts and optional widgets.
|
||||
- Add only backward-compatible database migrations before cutover.
|
||||
|
||||
## Outcomes
|
||||
|
||||
The completed program must:
|
||||
|
||||
1. Give every one of the 137 legacy routes a verified canonical destination or
|
||||
an explicit removal decision.
|
||||
2. Replace the fragmented admin and moderator surfaces with one capability-aware
|
||||
Command Deck.
|
||||
3. Deliver real workflows for all retained administration responsibilities, not
|
||||
wrappers around legacy pages.
|
||||
4. Provide global search, safe commands, derived operational inboxes, recent
|
||||
work, favorites, and optional widgets.
|
||||
5. Enforce the existing ACL model on navigation, reads, mutations, commands,
|
||||
search results, inbox items, and widgets.
|
||||
6. Produce durable and sanitized audit evidence for sensitive operations.
|
||||
7. Preserve a release-level rollback path without destructive database rollback.
|
||||
|
||||
## Delivery model
|
||||
|
||||
All work is committed to `codex/housekeeping-complete`, based on the latest
|
||||
`origin/main`. No pull request is opened until every vertical and the cutover are
|
||||
implemented, reviewed, and verified.
|
||||
|
||||
The existing `/admin-next` entry remains unavailable when
|
||||
`NODE_ENV=production`. Development and test environments use it to exercise the
|
||||
new shell before cutover. The final cutover changes the canonical `/admin` route;
|
||||
it does not weaken the production preview gate.
|
||||
|
||||
The branch is built in this order:
|
||||
|
||||
1. access, audit, error, and preference core;
|
||||
2. People, moderation, and support;
|
||||
3. Content and engagement;
|
||||
4. Economy and catalog;
|
||||
5. Hotel, world, and operational systems;
|
||||
6. Command Deck operations and cross-domain composition;
|
||||
7. atomic route cutover and legacy removal.
|
||||
|
||||
## Canonical route structure
|
||||
|
||||
After cutover the public administration route tree is:
|
||||
|
||||
```text
|
||||
/admin Operations workspace
|
||||
/admin/people/* users, tickets, CFH, bans, moderation, teams
|
||||
/admin/content/* articles, events, polls, media, engagement
|
||||
/admin/economy/* catalog, shop, transactions, vouchers, values
|
||||
/admin/hotel/* rooms, furni, badges, radio, emulator, Studio
|
||||
/admin/system/* settings, ACL, logs, DevOps, maintenance
|
||||
```
|
||||
|
||||
`/admin` is the operational home, not a duplicate menu page. `/mod` has no route
|
||||
after cutover. A workflow has one canonical owner and one canonical destination;
|
||||
the new tree must not retain duplicate hubs or aliases.
|
||||
|
||||
## Module ownership
|
||||
|
||||
`src/features/housekeeping/foundation` owns only cross-cutting composition:
|
||||
|
||||
- request-scoped actor and capability context;
|
||||
- registry and navigation projection;
|
||||
- Command Deck chrome and page-state primitives;
|
||||
- command dispatch contracts;
|
||||
- search and inbox orchestration;
|
||||
- preference reconciliation;
|
||||
- shared error and audit envelopes.
|
||||
|
||||
Each domain owns its routes, pages, query services, commands, search providers,
|
||||
inbox sources, widgets, and domain-specific validation. Domains communicate with
|
||||
the foundation through the published contracts. They do not import another
|
||||
domain's internal modules.
|
||||
|
||||
The foundation must not import database clients, server actions, or domain page
|
||||
modules. Server-only domain adapters may import data and action services.
|
||||
|
||||
## Domain manifests
|
||||
|
||||
Every manifest registers real, non-placeholder definitions for:
|
||||
|
||||
- canonical routes and contextual navigation;
|
||||
- safe and sensitive commands;
|
||||
- entity-search providers;
|
||||
- derived-inbox sources;
|
||||
- mandatory and optional widgets;
|
||||
- localization keys and capability requirements.
|
||||
|
||||
Registry validation rejects duplicate IDs across all provider categories,
|
||||
duplicate routes, invalid ownership, missing localization, unknown capability
|
||||
slugs, invalid widget kinds, and commands without an owning domain.
|
||||
|
||||
The migration matrix and manifests are linked by contract tests. Every retained
|
||||
matrix row must resolve to one registered route or workflow. Every manifest
|
||||
capability set must cover the capabilities attributed to its matrix rows.
|
||||
|
||||
## Authorization flow
|
||||
|
||||
Each request creates one capability context from `getAdminContext()`. The context
|
||||
contains the authenticated actor and immutable effective permission slugs.
|
||||
Rank is informational and may influence presentation defaults only; it is never
|
||||
used as a new authorization threshold.
|
||||
|
||||
Authorization is applied at every layer:
|
||||
|
||||
1. registry projection removes inaccessible domains and routes;
|
||||
2. provider orchestration calls only permitted providers;
|
||||
3. providers filter inaccessible results and items;
|
||||
4. page loaders revalidate their required capability;
|
||||
5. command execution revalidates capability and input on the server;
|
||||
6. the underlying mutation service retains its own permission guard.
|
||||
|
||||
Client state, hidden navigation, preferences, or a previously loaded page never
|
||||
authorize an operation.
|
||||
|
||||
## Commands and audit
|
||||
|
||||
Commands use typed input schemas and typed success/error results. Safe commands
|
||||
may execute directly from the palette. Sensitive commands open a dedicated
|
||||
contextual confirmation flow and require a reason when the command contract says
|
||||
so.
|
||||
|
||||
The existing `admin_audit_log` remains the canonical audit store. An additive
|
||||
migration adds nullable `correlation_id varchar(64)`, `outcome varchar(32)`,
|
||||
`reason text`, and `domain varchar(32)` columns plus an index on
|
||||
`correlation_id`. Existing `action`, `target`, `target_id`, `before`, `after`,
|
||||
`diff`, `ip_address`, and actor fields remain in use.
|
||||
|
||||
- Database mutations write mutation and audit evidence in the same transaction
|
||||
whenever the affected service uses the same database connection.
|
||||
- Sensitive external or file operations persist an audit intent before
|
||||
execution and a final outcome afterward. Failure to persist the intent blocks
|
||||
execution.
|
||||
- Audit payloads pass through the existing recursive secret redaction.
|
||||
- Every command result and audit record carries the same correlation ID.
|
||||
- Failed, denied, and partially completed sensitive operations are audited.
|
||||
|
||||
## Preferences
|
||||
|
||||
No suitable user-scoped HK preference store currently exists. Add
|
||||
`housekeeping_user_preferences` with:
|
||||
|
||||
- `user_id int` as the primary key and unique owner;
|
||||
- `schema_version int not null default 1`;
|
||||
- `payload longtext not null`, containing validated JSON presentation state;
|
||||
- `created_at datetime` and `updated_at datetime` timestamps.
|
||||
|
||||
The payload stores pinned route/command IDs, shortcut order, widget order, and
|
||||
enabled optional widget IDs. It never stores permissions, authorization
|
||||
decisions, workflow state, or inbox status.
|
||||
|
||||
Every read reconciles stored IDs against the current registry and effective
|
||||
capabilities. Unknown, removed, or unauthorized entries are dropped before the
|
||||
payload reaches the UI. Mandatory widgets cannot be disabled.
|
||||
|
||||
## Command Deck experience
|
||||
|
||||
The shell has four stable regions:
|
||||
|
||||
1. a compact six-domain rail;
|
||||
2. domain-owned contextual navigation;
|
||||
3. a global search and command field with keyboard access;
|
||||
4. an operational workspace for pages, inboxes, recent work, and widgets.
|
||||
|
||||
Desktop and mobile share the same semantic hierarchy. Mobile collapses the rail
|
||||
and contextual navigation without changing route ownership or available
|
||||
actions. Focus order, landmarks, headings, active-state uniqueness, keyboard
|
||||
navigation, reduced motion, and semantic theme tokens are tested contracts.
|
||||
|
||||
Loading, empty, partial, error, forbidden, and ready states use the shared page
|
||||
state primitives. Partial provider failure is visible without replacing valid
|
||||
results from other providers.
|
||||
|
||||
## Search
|
||||
|
||||
Search supports navigation, entity results, and commands. It is not a raw
|
||||
database search endpoint.
|
||||
|
||||
- A term shorter than two trimmed characters performs navigation/command
|
||||
matching only.
|
||||
- Entity providers have a two-second timeout and a maximum of 25 results each.
|
||||
- The combined entity response is capped at 50 results before client rendering.
|
||||
- Providers run only when their declared capability is satisfied.
|
||||
- Results include stable ID, owner, type, title, optional description, canonical
|
||||
href, and capability metadata.
|
||||
- Provider errors produce a typed partial result and do not fail unrelated
|
||||
providers.
|
||||
- Search terms and result payloads are not written to audit logs by default.
|
||||
|
||||
## Derived operational inbox
|
||||
|
||||
The inbox is a read model over domain-owned work: tickets, CFH reports, alerts,
|
||||
emulator errors, operational anomalies, and other existing live states. It does
|
||||
not introduce a second assignment or task-status system.
|
||||
|
||||
Each inbox item exposes stable source/item IDs, domain, type, title, priority,
|
||||
age, state, canonical href, available actions, and required capability. Source
|
||||
items are deduplicated by the pair `(sourceId, itemId)`.
|
||||
|
||||
Sources run independently with a two-second timeout. The composed response
|
||||
contains successful items plus per-source errors. The server caps the result at
|
||||
200 items after capability filtering and deterministic priority/age ordering.
|
||||
|
||||
## Recent work, favorites, and widgets
|
||||
|
||||
Recent work is derived from the operator's existing audit events and canonical
|
||||
route visits; it does not create workflow state. Favorites and ordering come
|
||||
from the reconciled preference payload.
|
||||
|
||||
Mandatory widgets are supplied by the system according to capability and cannot
|
||||
be removed. Optional widgets can be enabled and reordered. Widget loaders are
|
||||
server-side, capability-checked, independently timed out, and represented as
|
||||
partial failures rather than shell failures.
|
||||
|
||||
## Vertical scope
|
||||
|
||||
### Access, audit, and system core
|
||||
|
||||
- command dispatcher and confirmation model;
|
||||
- audit extension and correlation IDs;
|
||||
- typed error taxonomy and boundary mapping;
|
||||
- preference repository and reconciliation;
|
||||
- shared provider orchestration and timeout behavior;
|
||||
- System routes for ACL, settings, logs, DevOps, and maintenance.
|
||||
|
||||
### People, moderation, and support
|
||||
|
||||
- user discovery, details, editing, password/reset controls, account relations,
|
||||
bans, and permitted staff actions;
|
||||
- help tickets and moderator tickets;
|
||||
- CFH queues and details;
|
||||
- moderation actions, team views, and ban workflows;
|
||||
- People search providers, inbox sources, commands, and widgets.
|
||||
|
||||
This vertical proves that all retained `/mod` responsibilities work inside the
|
||||
new capability model before `/mod` is removed.
|
||||
|
||||
### Content and engagement
|
||||
|
||||
- articles, events, polls, media, navigation content, tags, banners, and related
|
||||
editorial tools;
|
||||
- Content search, commands, inbox sources, and widgets;
|
||||
- consolidation of duplicate editorial hubs into canonical workflows.
|
||||
|
||||
### Economy and catalog
|
||||
|
||||
- catalog and item management, Builder Club catalog, maintenance, shop,
|
||||
transactions, vouchers, subscriptions, marketplace, and value tools;
|
||||
- Economy search, commands, anomaly sources, and widgets;
|
||||
- existing specialized editors remain components of canonical workflows rather
|
||||
than parallel navigation roots.
|
||||
|
||||
### Hotel, world, and operational systems
|
||||
|
||||
- rooms and room furni, badges, sounds, radio, emulator controls, imports, and
|
||||
Studio tools;
|
||||
- Hotel search, commands, operational sources, and widgets;
|
||||
- long-running operations retain progress/error behavior and gain consistent
|
||||
capability and audit envelopes.
|
||||
|
||||
### Operations composition
|
||||
|
||||
- global search and command palette;
|
||||
- derived inbox and partial-source reporting;
|
||||
- recent work and favorites;
|
||||
- mandatory operational summaries and optional widgets;
|
||||
- no duplicate mutation logic: actions route to the owning domain command.
|
||||
|
||||
## Error model
|
||||
|
||||
All HK services return typed errors from this stable set:
|
||||
|
||||
- `UNAUTHENTICATED`;
|
||||
- `FORBIDDEN`;
|
||||
- `VALIDATION`;
|
||||
- `NOT_FOUND`;
|
||||
- `CONFLICT`;
|
||||
- `RATE_LIMITED`;
|
||||
- `DEPENDENCY_UNAVAILABLE`;
|
||||
- `TIMEOUT`;
|
||||
- `INTERNAL`.
|
||||
|
||||
User messages are localized and do not expose internal details. Server logs and
|
||||
audit evidence include correlation IDs. Expected domain errors do not rely on
|
||||
framework exception text. Unknown errors are sanitized at the boundary and
|
||||
logged once.
|
||||
|
||||
## Database changes
|
||||
|
||||
Allowed pre-cutover migrations are additive only:
|
||||
|
||||
1. nullable HK audit metadata columns on `admin_audit_log`;
|
||||
2. the `housekeeping_user_preferences` table and its unique user index.
|
||||
|
||||
No legacy table or column is dropped or repurposed in this program. Removal of
|
||||
legacy UI routes is an application cutover, not a destructive data migration.
|
||||
|
||||
## Atomic cutover
|
||||
|
||||
The final cutover commit is created only after all vertical gates pass. It:
|
||||
|
||||
1. moves the completed shell and Operations workspace to `/admin`;
|
||||
2. changes domain preview hrefs to canonical `/admin/<domain>` hrefs;
|
||||
3. updates internal links, navigation configuration, and authorization fallback
|
||||
destinations;
|
||||
4. removes `/mod` and every legacy route marked `REMOVE`;
|
||||
5. removes legacy pages whose behavior moved or merged into canonical routes;
|
||||
6. removes the temporary preview entry and flag if no longer used by tests;
|
||||
7. adds no compatibility redirects.
|
||||
|
||||
The cutover must leave no links, imports, route discovery entries, or tests that
|
||||
depend on removed UI modules.
|
||||
|
||||
## Verification strategy
|
||||
|
||||
Each vertical uses TDD and has four gates:
|
||||
|
||||
1. contract and authorization tests;
|
||||
2. domain query/command behavior tests, including denied and failure paths;
|
||||
3. page and accessibility behavior tests;
|
||||
4. cumulative Housekeeping and repository verification.
|
||||
|
||||
The final branch requires:
|
||||
|
||||
- the migration matrix reporting 137/137 valid with every retained row linked to
|
||||
a canonical implementation;
|
||||
- mutation-sensitive authorization, provider, command, audit, and preference
|
||||
tests;
|
||||
- full project tests, Housekeeping tests, typecheck, semantic Biome, targeted
|
||||
formatting checks, and `git diff --check`;
|
||||
- production build with temporary environment restoration;
|
||||
- visual verification at desktop and mobile widths for every domain and shared
|
||||
state;
|
||||
- route-level smoke checks for canonical pages, denied access, and removal of
|
||||
`/mod`/obsolete routes;
|
||||
- a broad whole-branch code review followed by one reviewed fix wave if needed.
|
||||
|
||||
Repository-wide pre-existing formatter debt is reported separately and must not
|
||||
be hidden by mass-formatting unrelated files.
|
||||
|
||||
## Merge, deployment, and rollback
|
||||
|
||||
The single pull request targets `main` only after all final gates pass. Merging
|
||||
is the atomic release boundary; no partial vertical is intentionally exposed to
|
||||
production operators.
|
||||
|
||||
After merge, the deployment pipeline must complete and `/api/health` must be
|
||||
verified live. A push or successful build alone is not deployment evidence.
|
||||
|
||||
Rollback deploys the prior application release. Because database changes are
|
||||
additive and ignored by the prior release, rollback does not require manual data
|
||||
reversal. If audit or preference migrations themselves fail, deployment stops
|
||||
before serving the cutover release.
|
||||
|
||||
## Explicit non-goals
|
||||
|
||||
- A new task-assignment system for inbox items.
|
||||
- A replacement authentication or ACL model.
|
||||
- Rank-based authorization thresholds.
|
||||
- Compatibility redirects for removed admin/mod routes.
|
||||
- Destructive cleanup of legacy database data.
|
||||
- Rewriting specialized domain engines that already work; they are integrated
|
||||
behind consistent domain contracts instead.
|
||||
- Unrelated CMS redesign or repository-wide formatting cleanup.
|
||||
|
||||
## Completion criteria
|
||||
|
||||
The program is complete only when:
|
||||
|
||||
- all retained legacy capabilities are available through canonical new routes;
|
||||
- all six manifests contain real routes/providers/widgets rather than empty
|
||||
placeholders;
|
||||
- the Command Deck search, commands, inbox, preferences, recent work, and widgets
|
||||
operate against real domain services;
|
||||
- capability enforcement and audit evidence cover every exposed read and
|
||||
mutation path;
|
||||
- `/admin` serves the new HK, `/mod` and removed legacy routes are unreachable,
|
||||
and no compatibility redirects exist;
|
||||
- final local, CI, deployment, health, and visual evidence are all recorded.
|
||||
Reference in new issue
Block a user