# Task 12 — People users, community, and staff workflows Status: DONE ## Delivered scope - Registered exactly the nine approved real People routes: users list/edit/multi-account/detail, community online/guilds/guild detail, and staff applications/teams. The remaining support and moderation routes stay catalogued in `routes.ts` but unregistered for Task 13. - Added query-backed People pages with explicit loading, empty, partial, dependency-error, forbidden, and ready states. Links are canonical `/ase/people/*` links; optional mail/IP fields and mutation affordances remain absent unless their exact capability is present. - Added the exact fourteen user command IDs plus the six stable People-owned IDs `people.guild.disband`, `people.application.decide`, `people.team.change`, `people.ip.action`, `people.vpn.configure`, and `people.word-filter.update`. - Added bounded Zod command schemas, stable rate limits, dispatcher capability rechecks, confirmation metadata, a redirect-free server-only mutation service, and deterministic bootstrap registration. - Extracted shared mutation behavior behind the existing actions while preserving the legacy action exports, exact ACLs, `/admin` revalidation, VPN redirect/fail-soft behavior, word-filter `ActionResult` shapes, already-gone delete semantics, and failure propagation where legacy persistence errors previously propagated. - Added neutral EN/IT labels only for the nine runtime routes. ## Security and behavior decisions - No ACL slug or route authorization rank threshold was added. Exact legacy capabilities remain authoritative: single ban/unban use `USERS_BAN`, reset-password uses `USERS_RESET_PASSWORD`, bulk/user/community/team/application operations use `USERS_EDIT`, IP/VPN use `SETTINGS_EDIT`, and word filter uses `WORDFILTER_EDIT`. - The existing target hierarchy safeguard remains for legacy user mutations that previously used `guardRank`; alert remains capability-authorized without a new target-rank rule. - Ordinary user edit and alert remain reason-free because their existing semantics are non-destructive. Sanctions, destructive operations, global/security changes, currency delivery, and bulk mutations require a nonblank dispatcher reason. Ban and bulk-ban operational reasons are also persisted with the mutation audit evidence. - Every public service call rechecks the exact capability before production work. The production adapter is module-private; server-only placement is not treated as authorization. - Successful mutations emit before/after audit evidence and a stable correlation ID. Audit persistence failure is fail-closed and maps to `DEPENDENCY_UNAVAILABLE`. User mutation audit snapshots omit mail because it is unnecessary PII; page projection continues to follow Task 11 exactly. - User pages consume only Task 11 guarded read models, preserving bounded pagination, deterministic sorting, fail-closed DTO validation, serialization, zero-sentinel rules, watched state, permission context, and PII projection. - Legacy wrappers remain on `/admin` behavior until Task 25. No `/admin`, `/mod`, API, redirect, database schema, deployment, or cutover behavior was changed. ## Strict TDD evidence ### Initial command/page/route RED ```text pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/commands/user-commands.test.ts src/features/housekeeping/domains/people/commands/community-commands.test.ts src/features/housekeeping/domains/people/pages/people-primary-pages.test.tsx Test Files 3 failed (3) Tests 0 Missing modules: community-commands, ../services/mutations, ../route-handlers ``` Initial focused GREEN: ```text Command tests: 2 files passed, 22 tests passed Primary page/route tests: 3 files passed, 12 tests passed Bootstrap tests: 1 file passed, 2 tests passed ``` ### Foundation integration RED/GREEN RED after enabling People: ```text pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people src/features/housekeeping/foundation Test Files 3 failed | 31 passed Tests 4 failed | 376 passed Failures: stale System-only registry assertions, stale preview expectation, and an unapproved People vertical runtime edge. ``` GREEN after updating the explicit runtime-edge and registry contracts: ```text Focused foundation contracts: 3 files passed, 40 tests passed People + foundation: 34 files passed, 380 tests passed ``` ### Audited sanction reason RED: ```text pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/services/mutations-reason-production.test.ts Test Files 1 failed (1) Tests 1 failed (1) The ban audit after-snapshot did not contain the nonblank sanction reason. ``` GREEN: ```text Production mutation contracts: 2 files passed, 4 tests passed The audited snapshot includes the reason and excludes mail. ``` ### Legacy wrapper failure parity RED: ```text pnpm exec vitest run --coverage.enabled=false src/actions/people-wrapper-errors.test.ts Test Files 1 failed (1) Tests 3 failed (3) Persistence failures were swallowed and already-gone word-filter deletion was not idempotent. ``` GREEN: ```text Test Files 1 passed (1) Tests 3 passed (3) ``` ### Single authorization check for positive bulk adjustment RED: ```text pnpm exec vitest run --coverage.enabled=false src/actions/bulk-adjust-wrapper.test.ts Test Files 1 failed (1) Tests 1 failed (1) Expected one requirePermission call; received two. ``` GREEN: ```text Test Files 1 passed (1) Tests 1 passed (1) ``` ### Static gates during implementation ```text pnpm typecheck RED: one unused `describe` import in admin-ip.test.ts GREEN: tsc --noEmit, exit 0 pnpm exec biome check --formatter-enabled=false RED: 14 import-order assists GREEN: checked 44 files, no fixes applied ``` ## Final verification ```text Focused wrapper/command/page/service/route/authorization/audit matrix Test Files 22 passed (22) Tests 129 passed (129) pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people src/features/housekeeping/foundation Test Files 35 passed (35) Tests 381 passed (381) pnpm test:housekeeping Test Files 54 passed (54) Tests 469 passed (469) pnpm typecheck tsc --noEmit Exit 0 pnpm exec biome check --formatter-enabled=false <44 exact changed Task 12 src files> Checked 44 files. No fixes applied. git diff --check Exit 0 ``` The Node engine warning remains the approved non-blocker: the repository requests Node `>=26.8.1 <27`, while this host runs Node `v26.7.0` with pnpm `11.24.0`. All test and type gates exited successfully. No database operation, deployment, push, or pull-request update was performed. ## Official review fix round 1 The official review reported 0 Critical and 5 Important findings. This round addresses the five findings without widening the Task 12 route catalog or changing legacy redirects, safe-action response shapes, or cutover behavior. ### RED evidence ```text Production server authority: auth resolver was called 0 times at the public service boundary (1 failing regression). Canonical external audit: 3 failing regressions for missing durable intent/outcome behavior. Transactional audit: expected one transaction and observed zero (1 failing regression). Legacy/production workflows: new production matrix initially exposed bulk truncation/deduplication, trade-lock hierarchy/state, missing StaffActivities, and missing word-filter refresh behavior. Primary workflows: page suite started at 7 passed / 2 failed (no executable command form and no bounded URL parser); preview contract started at 61 passed / 3 failed (searchParams/loading propagation). Import boundary after real forms: test:housekeeping reached 481 passed / 1 failed, then the focused boundary exposed one exact page-state -> People models edge (22 passed / 1 failed). ``` ### GREEN implementation - Production People services now rehydrate `getHousekeepingCapabilityContext()` on every public mutation. Invocation data can carry correlation and an expected actor only; it cannot synthesize permissions. The production adapter stays private, while test factories inject an authority resolver. - Pure database mutations write their canonical before/after audit evidence in the same transaction. Mixed database/RCON/cache work writes sanitized intent first and a correlated success, failure, or partial outcome afterward. Audit-outcome persistence errors retain truthful completed/partial state, and legacy wrappers preserve their observable behavior. - Ban/unban use observed active-ban state; trade-lock uses observed sanction/settings state. Passwords, hashes, API keys, and secrets are excluded from canonical evidence. - Shared legacy bulk paths preserve original order, duplicates, totals, and iteration with no service-side 100-item cap. The <=100 bound remains in command schemas. Trade lock has no invented hierarchy gate and its missing-user wrapper message remains exactly `User not found`. - Original `StaffActivities` side effects are retained for bulk ban/unban/currency/badge, guild disband, VPN, and trade lock. Missing word-filter deletion still reloads local cache, sends RCON refresh, and returns legacy success. - The nine registered pages now expose capability-gated, accessible command forms backed by `executeHousekeepingCommand`; no inert command spans remain. Edit submits a mutation, list inputs come from bounded URL search parameters, and the dynamic preview route passes them through. Atomic Task 11 queries keep their fail-closed contracts; the impossible synthetic partial state was removed. A real Next loading route was added. - The foundation contract allows only the exact same-domain edges required here: each People page to the shared People command form, the form to the single housekeeping command action, and page-state to the People `ListInput` model. No wildcard or prefix relaxation was introduced. ### Final verification after review fixes ```text Focused wrapper/command/page/service/route/auth/audit/staff-smoke matrix Test Files 24 passed (24) Tests 187 passed (187) pnpm test:housekeeping Test Files 58 passed (58) Tests 482 passed (482) pnpm test Test Files 206 passed | 3 skipped (209) Tests 1321 passed | 5 skipped (1326) pnpm typecheck tsc --noEmit Exit 0 pnpm exec biome check --formatter-enabled=false <40 exact changed Task 12 source files> Checked 40 files. No fixes applied. git diff --check e1b31ff7738eb5cc59e7765c8ed7290d62130972 -- Exit 0 ``` The approved Node engine warning remains: the repository requests Node `>=26.8.1 <27`, while the host runs Node `v26.7.0` with pnpm `11.24.0`. No database operation, deployment, push, or pull-request update was performed. ## Official review fix round 2 The round-1 re-review reported 0 Critical, 7 Important, and no Minor findings. This round addresses all seven findings without changing the nine-route Task 12 manifest or exposing any raw production adapter. ### RED evidence ```text Typed partial/result contract: 5 failed / 8 passed before completion metadata and audit-outcome handling were added. Observed active-ban and absent-settings snapshots: 2 focused failures before deterministic active reads and null-preserving trade snapshots. BIGINT preservation: 8 focused failures across application, team, IP, and wordfilter before decimal string/BigInt boundaries. Real form/reset workflow: 2 primary-page failures before the actual action adapter and one-time credential result were added. Production operation closure: 2 failed / 10 passed before unban observed-after and bulk false-RCON partial truth. Post-commit notification/legacy parity: 2 failed / 12 passed before update/reset transactional intent and legacy throw/false mapping. Cumulative gate exposed one unsupported custom Zod schema, one stale direct-alert expectation, and one stale numeric audit-ID expectation; each received a minimal regression-preserving fix. ``` ### GREEN implementation - Mixed database/external operations now commit sanitized intent with the mutation and return correlated typed `partial` completion when RCON, cache, notification, or final audit persistence fails afterward. Pre-mutation external failure and intent persistence failure remain blocking. The dispatcher and public server action preserve one serializable partial result and emit no contradictory generic failure evidence. - Ban and unban reuse the permanent-or-unexpired Task 11 filter, deterministic timestamp/ID ordering, and observed before/after reads. An absent `UsersSettings` row remains null before and after a no-op trade settings update. - Legacy alert calls RCON without a target query or hierarchy guard. Legacy wrappers retain their prior false/throw behavior while new Housekeeping commands report synchronization false as partial truth. - Application, team, IP, and wordfilter identifiers remain canonical decimal strings/`BigInt` through wrappers and Drizzle, including values above `Number.MAX_SAFE_INTEGER`. The cloneable command regex accepts the full unsigned BIGINT range and rejects overflow without a Zod custom refinement. - Reset-password returns the generated credential once in the current authorized form result. It is rendered through an accessible `output`, excluded from durable service evidence, and recursively redacted by the canonical audit sanitizer. - Production tests execute all twenty Task 12 operation IDs with meaningful database/RCON/audit assertions. The primary-page test invokes the actual form action adapter, and the unused multi-accounts-to-command-form boundary exception was removed. ### Final verification after review fix round 2 ```text Focused People + foundation + wrappers + audit + action + staff-smoke matrix Test Files 45 passed (45) Tests 459 passed (459) pnpm test:housekeeping Test Files 58 passed (58) Tests 502 passed (502) pnpm test Test Files 206 passed | 3 skipped (209) Tests 1345 passed | 5 skipped (1350) pnpm typecheck tsc --noEmit Exit 0 pnpm exec biome check --formatter-enabled=false <28 exact changed TypeScript/TSX files> Checked 28 files. No fixes applied. ``` The approved Node engine warning remains: the repository requests Node `>=26.8.1 <27`, while the host runs Node `v26.7.0` with pnpm `11.24.0`. No database operation, deployment, push, or pull-request update was performed. ## Official review fix round 3 The round-2 re-review reported 0 Critical, 2 Important, and 2 adjacent Minor findings. This round addresses all four findings without changing the nine-route manifest, the public command IDs, or legacy external call ordering and permissions. ### RED evidence ```text Focused external-audit, bulk-production, and dispatcher matrix Test Files 2 failed (2) Tests 15 failed | 54 passed (69) The ten external-only false/throw cases persisted optimistic desired after-state instead of confirmed unchanged or unknown delivery evidence. Four bulk currency/badge false/throw cases reported completed=0 and Database error after a committed database write. The dispatcher accepted one ok:false result carrying impossible completion metadata. ``` ### GREEN implementation - External-only alert, disconnect, mute, unmute, and send-currency keep desired state in intent/success evidence. Confirmed RCON `false` now writes a dedicated unchanged/no-delivery failure snapshot; an exception writes unknown delivery with a null after-state. Correlation and failure outcome stay identical across intent/outcome records. - Bulk currency and badge count a successful database write before RCON. RCON false/throw is additive `externalSyncFailures` sync debt, never a database failure; `failedIds` remains reserved for database/business failures and the result is typed partial with an explicit no-automatic-retry warning. - Webhook notification remains explicit fire-and-forget best effort (`void notify(...)`) and no longer participates in mutation completion. The impossible promise-rejection test was replaced with the real void contract. - The dispatcher runtime schema now accepts `completion` only for `ok: true`, matching the TypeScript `HousekeepingResult` contract; failure envelopes containing it are rejected as malformed. ### Final verification after review fix round 3 ```text Focused external audit + production bulk + dispatcher Test Files 3 passed (3) Tests 73 passed (73) People + foundation + legacy wrappers + staff-smoke matrix Test Files 48 passed (48) Tests 470 passed (470) pnpm test:housekeeping Test Files 58 passed (58) Tests 516 passed (516) pnpm test Test Files 206 passed | 3 skipped (209) Tests 1359 passed | 5 skipped (1364) pnpm typecheck tsc --noEmit Exit 0 pnpm exec biome check --formatter-enabled=false <4 exact changed source/test files> Checked 4 files. No fixes applied. git diff --check Exit 0 ``` The approved Node engine warning remains: the repository requests Node `>=26.8.1 <27`, while the host runs Node `v26.7.0` with pnpm `11.24.0`. No database operation, deployment, push, or pull-request update was performed. ## Official review fix round 4 The round-3 re-review reported 0 Critical, 2 Important, and 0 Minor findings. This round restores the pre-cutover legacy bulk result contract at the wrapper boundary and makes committed-database/emulator-sync debt explicit in the successful partial operator result. Canonical Housekeeping accounting, audit evidence, routes, commands, and ACLs remain unchanged. ### Pre-Task12 parity evidence `git show e1b31ff7^:src/actions/bulk-users.ts` confirms that currency and badge wrappers awaited RCON inside the same `try`: an RCON exception entered the catch, did not increment `given`, and appended `{ userId, reason: "Database error" }`; an RCON `false` return did not throw and therefore remained a legacy success. Positive bulk adjustment delegated to the same currency wrapper and had the same result semantics. ### RED evidence ```text pnpm exec vitest run --coverage.enabled=false src/actions/bulk-users.test.ts src/actions/bulk-adjust-wrapper.test.ts Test Files 2 failed (2) Tests 3 failed | 3 passed (6) Currency, badge, and positive-adjust wrappers returned given/adjusted=1 with no failedIds for the canonical external-sync debt produced by a thrown RCON call; historical results require 0 plus Database error. pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/pages/people-primary-pages.test.tsx Test Files 1 failed (1) Tests 2 failed | 12 passed (14) The operator result rendered only Partially completed and exposed neither an alert/do-not-retry instruction nor the typed user sync debt returned by the real form adapter. ``` ### GREEN implementation - `src/actions/bulk-users.ts` translates only `externalSyncFailures` at the legacy wrapper boundary into historical `Database error` failures and subtracts those entries from `given`/positive `adjusted`. Canonical completed counts and sync-debt evidence are untouched; the existing legacy `false` path still produces no external-sync entry and remains successful. Failure entries are restored in input order, including duplicate IDs. - `src/features/housekeeping/domains/people/pages/people-command-form.tsx` reads only a successful typed partial result with failed external completion and a bounded `after.externalSyncFailures` array. It renders an alert, explicit do-not-retry instruction, and safe user/reason debt entries. Unknown payload fields and malformed entries are never rendered, and the generated reset password path remains one-time and unchanged. Focused GREEN: ```text pnpm exec vitest run --coverage.enabled=false src/actions/bulk-users.test.ts src/actions/bulk-adjust-wrapper.test.ts Test Files 2 passed (2) Tests 6 passed (6) pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/pages/people-primary-pages.test.tsx Test Files 1 passed (1) Tests 14 passed (14) pnpm exec vitest run --coverage.enabled=false src/actions/bulk-users.test.ts src/actions/bulk-adjust-wrapper.test.ts src/features/housekeeping/domains/people/pages/people-primary-pages.test.tsx src/features/housekeeping/domains/people/services/mutations-production-workflows.test.ts Test Files 4 passed (4) Tests 48 passed (48) ``` ### Cumulative verification ```text People + foundation + Task12 legacy wrappers + route/audit/staff-smoke matrix Test Files 52 passed (52) Tests 491 passed (491) pnpm test:housekeeping Test Files 58 passed (58) Tests 518 passed (518) pnpm test Test Files 206 passed | 3 skipped (209) Tests 1364 passed | 5 skipped (1369) pnpm typecheck tsc --noEmit Exit 0 pnpm exec biome check --formatter-enabled=false src/actions/bulk-users.ts src/actions/bulk-users.test.ts src/actions/bulk-adjust-wrapper.test.ts src/features/housekeeping/domains/people/pages/people-command-form.tsx src/features/housekeeping/domains/people/pages/people-primary-pages.test.tsx Checked 5 files. No fixes applied. git diff --check Exit 0 ``` The only warning is the approved Node engine mismatch: the repository requests Node `>=26.8.1 <27`, while the host runs Node `v26.7.0` with pnpm `11.24.0`. ### Exact tracked paths - `.superpowers/sdd/2026-08-26-housekeeping-completion/task-12-report.md` - `src/actions/bulk-adjust-wrapper.test.ts` - `src/actions/bulk-users.test.ts` - `src/actions/bulk-users.ts` - `src/features/housekeeping/domains/people/pages/people-command-form.tsx` - `src/features/housekeeping/domains/people/pages/people-primary-pages.test.tsx` The required controller lines were appended to the git-ignored `.superpowers/sdd/2026-08-26-housekeeping-completion/progress.md`; it is excluded from the commit. `.remember/` remains untouched. ### Self-review - Scope and compatibility: the production mutation service, canonical audit/accounting, manifest, route, command, ACL, database, redirect, and cutover behavior are unchanged. The adapter applies only to legacy currency/badge results and their historical positive-adjust delegate. - Security: the operator surface requires an `ok: true` partial/external-failed envelope, accepts at most 100 positive safe-integer user IDs, renders only the canonical safe reason, and does not inspect or serialize arbitrary result payloads. Reset-password display and audit redaction tests remain green. - Test quality: legacy tests exercise the real exported wrappers against a complete canonical partial response and fail on either wrong count or missing historical failure; UI tests render the real component, invoke the real form adapter, and prove malformed/extra payload is not displayed. ### Commit Single local commit message: `fix(housekeeping): restore people partial compatibility`. The final SHA of the commit containing this report is returned to the controller after creation. No database operation, deployment, push, pull, or pull-request update was performed.