310 lines
17 KiB
Markdown
310 lines
17 KiB
Markdown
# 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 <exact Task 12 src files>
|
|
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. |