15 KiB
Task 11 — People workflow read models
Status: DONE — fix round 2
Delivered scope
- Added the exact 24-route People catalog covering the 39 migration-matrix entries across users, multi-account review, online/community, guilds, staff applications/teams, support tickets/help tickets, CFH, moderation overview, bans, IP rules, VPN settings, and word filter workflows.
- Added canonical, JSON-serializable People DTOs,
/ase/peoplelink builders, bounded list normalization, and stable sorting with numeric-ID tie breaking. - Added injected query factories and narrow server-only production adapters for user/detail, community/guild, staff/applications/teams, support queues/tickets/help/CFH, and moderation/bans/sanctions sources.
- Reused foundation
HousekeepingResult, error codes, capability context, authorization, and canonical href contracts. People-localListInputandPagewere added because no shared foundation equivalents exist in this checkout. - Kept the People manifest, global handlers, pages, mutations, providers, widgets, search, and inbox unchanged for Task 12.
Security and behavior decisions
- User mail and current IP remain independently nullable fields. Each is projected only when the capability context contains the existing
PERMS.USERS_VIEW;PERMS.MOD_USERS_VIEWalone receives the safe base projection with both values set tonull, and a context with neither permission is forbidden. - No new ACL slug or rank threshold was introduced. Staff filtering reuses the existing
getMinStaffRank()source. - The production user selection is explicit and excludes passwords, authentication tickets, secrets, and two-factor material. VPN settings intentionally exclude
vpn_api_key. - Adapters fail closed. Malformed driver envelopes, invalid identifiers, corrupt links, non-serializable DTO values, and count failures map to
DEPENDENCY_UNAVAILABLE. Primary/entity identifiers remain positive safe integers; zero is accepted only for the schema-declared guilduserId/roomIdand CFHsenderId/reportedId/roomId/moderatorIdsentinels. Missing valid detail entities map toNOT_FOUND; invalid request identifiers map toVALIDATION. - Pagination clamps page size to 100 and offset to 10,000. Deterministic primary sorting, numeric-ID tie breaking, and
LIMIT/OFFSETnow execute in the database; no list query fetches a prefix for locale re-sorting or second slicing. - Raw production adapters and
buildPeopleUserSelectionare module-private. Runtime exports expose only context-authorized query factories and singleton query surfaces. - Multi-account clusters use one bounded CTE/window page query plus one independent matching-cluster count query, cap accounts per cluster at 100, and never issue one query per IP cluster.
- User detail/edit now includes the operator's watched state and canonical permission-rank data. Support ticket reads use the existing unified inbox through a strict, fail-closed, database-paged mode that includes CMS and help-center rows while leaving the legacy tolerant mode unchanged.
- The unified
/support/ticketsinbox still merges CMS and help-center rows. The ticket desk now has its own strict CMS-only page loader preserving priority, category, assignee, and message count; help summaries include reply count. Support desk/detail DTOs explicitly include queue counts, bounded staff, and the relevant active ban. - Active bans are filtered before sorting. Expiry
0remains the permanent-active sentinel; expired rows cannot hide permanent or future-active bans in lists or details.
Official fix round 1 findings
- Authorization boundary: fixed by making all raw production adapters and the user selection builder module-private and testing only guarded public query surfaces plus source/runtime export contracts.
- Pagination and sorting: fixed by moving declared sort fields, deterministic tie ordering, and bounded
LIMIT/OFFSETto production adapters. The exactuser10/user2and supportstatus/updatedAtpage regressions are covered. - Resource bounds: fixed by replacing multi-account prefix loading plus per-row
Promise.allwith one bounded batched CTE/window query. No million-row prefix and no N+1 cluster query remain. - Fail-closed database validation: fixed across People models and community/support/moderation query boundaries. Invalid driver shapes and invalid identifiers cannot become empty lists,
NOT_FOUND, ID0, or corrupt canonical links. - Canonical dependencies: fixed watched and permission-rank data for user detail/edit;
/support/ticketsnow calls strictfetchUnifiedTicketInbox; support queue/staff/active-ban data is explicit in canonical query DTOs. - Moderation capability equality: fixed after direct user authorization.
moderationQuery.capabilitynow exactly equals the existing eleven-slug overview union already used by the route andrun; no route, mutation, rank threshold, or new slug changed. - Active bans: fixed list/detail selection so permanent
ban_expire = 0and future-active bans are deterministic and expired rows cannot hide them.
The two official Minor findings remain parked and unchanged as instructed.
Official fix round 2 findings
- Entity-aware sentinels: canonical guild DTOs now use the real schema names
userIdandroomId. Serialization permits zero only on those two guild fields and the four named CFH fields when the containing DTO has the matching canonical entity href. Generic*Idzero values, negatives, unsafe integers, and primary ID zero remain unavailable failures. - Support source fidelity:
people.support.ticketsremains on strictfetchUnifiedTicketInbox.people.support.ticket-desknow dispatches to a distinct strictwebsite_ticketsloader with message aggregation and no help-center source. Real priority/category/assignee/message count and help reply count are present in canonical DTOs; malformed driver rows fail closed. - Multi-account total: the page CTE and matching-cluster count run as two bounded parallel queries. Empty pages retain the correct total without prefix loading or N+1 queries.
Strict TDD evidence
Cycle 1 — exact route catalog
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/routes.test.ts
Test Files 1 failed
Error: Cannot find module './routes'
GREEN:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/routes.test.ts
Test Files 1 passed (1)
Tests 3 passed (3)
Cycle 2 — canonical models and normalizers
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/models.test.ts
Test Files 1 failed
Error: Cannot find module './models'
GREEN:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/models.test.ts
Test Files 1 passed (1)
Tests 5 passed (5)
Cycle 3 — injected-adapter read queries
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-queries.test.ts
Test Files 1 failed
Error: Cannot find module './community'
GREEN:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-queries.test.ts
Test Files 1 passed (1)
Tests 10 passed (10)
Production-source contract RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts
Test Files 1 failed (1)
Tests 2 failed (2)
Reason: raw production adapters and buildPeopleUserSelection were exported as bypassable runtime internals.
Pagination regression RED after adding the production contract fixture:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
Expected ["203.0.113.1", "203.0.113.2"], received ["203.0.113.2"].
GREEN for the original baseline implementation:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts
Test Files 1 passed (1)
Tests 3 passed (3)
The query tests cover adversarial page size/offset/search, stable tie sorting, empty/missing entities, adapter and count failures, PII capability combinations, serializable DTOs, explicit source projection, and production pagination.
Fix round 1 strict behavioral TDD evidence
Authorization boundary
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts
Test Files 1 failed (1)
Tests 2 failed (2)
Observed runtime exports: buildPeopleUserSelection and peopleUsersAdapters.
GREEN:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts
Test Files 1 passed (1)
Tests 3 passed (3)
Database pagination and declared sorting
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/models.test.ts src/features/housekeeping/domains/people/queries/people-queries.test.ts
Test Files 2 failed (2)
Tests 4 failed | 14 passed (18)
Failures: offset 999999 was not capped; DB pages were sliced a second time for user10/user2 and support status/updatedAt.
GREEN:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/models.test.ts src/features/housekeeping/domains/people/queries/people-queries.test.ts
Test Files 2 passed (2)
Tests 18 passed (18)
Multi-account resource bounds
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts
Test Files 1 failed (1)
Tests 1 failed | 2 passed (3)
Observed prefix result plus one query per IP cluster.
GREEN:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts
Test Files 1 passed (1)
Tests 3 passed (3)
Fail-closed validation
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/models.test.ts src/features/housekeeping/domains/people/queries/people-queries.test.ts src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts
Test Files 3 failed (3)
Tests 5 failed | 21 passed (26)
Failures covered ID 0, corrupt links/dates, corrupt detail shapes, and malformed driver envelopes.
GREEN:
same command
Test Files 3 passed (3)
Tests 26 passed (26)
Canonical dependencies and unified support inbox
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-queries.test.ts src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts -t "watched state|hydrates desk|strict unified"
Test Files 2 failed (2)
Tests 3 failed | 22 skipped (25)
GREEN:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/models.test.ts src/features/housekeeping/domains/people/queries/people-queries.test.ts src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts -t "watched state|hydrates desk|strict unified|normalizes BigInt"
Test Files 3 passed (3)
Tests 4 passed | 27 skipped (31)
Moderation capability equality
RED captured before direct authorization:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-queries.test.ts -t "eleven-permission"
Test Files 1 failed (1)
Tests 1 failed | 18 skipped (19)
Expected the route/run eleven-slug union; query metadata still contains six slugs.
GREEN after the user directly authorized only this isolated metadata hunk:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-queries.test.ts -t "eleven-permission"
Test Files 1 passed (1)
Tests 1 passed | 18 skipped (19)
Active-ban semantics
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts -t "permanent"
Test Files 1 failed (1)
Tests 1 failed | 4 skipped (5)
Expected permanent expiresAt 0; received null.
GREEN:
same command
Test Files 1 passed (1)
Tests 1 passed | 4 skipped (5)
Fix round 2 strict behavioral TDD evidence
Entity-aware schema sentinels
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-queries.test.ts -t "zero sentinels"
Test Files 1 failed (1)
Tests 2 failed | 19 skipped (21)
Valid guild userId/roomId zero and CFH senderId/reportedId/moderatorId/roomId zero were rejected by generic identifier validation.
GREEN:
same command
Test Files 1 passed (1)
Tests 2 passed | 19 skipped (21)
CMS-only ticket desk and help reply counts
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-queries.test.ts src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts -t "CMS-only context loader|reply counts"
Test Files 2 failed (2)
Tests 2 failed | 28 skipped (30)
The desk received a help row with synthetic normal priority; help summary omitted replyCount.
GREEN:
same command
Test Files 2 passed (2)
Tests 2 passed | 28 skipped (30)
Independent multi-account total
RED:
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts -t "beyond the last page"
Test Files 1 failed (1)
Tests 1 failed | 8 skipped (9)
Expected total 4 on the empty page; received 0.
GREEN:
same command
Test Files 1 passed (1)
Tests 1 passed | 8 skipped (9)
Verification
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people/routes.test.ts src/features/housekeeping/domains/people/models.test.ts src/features/housekeeping/domains/people/queries/people-queries.test.ts src/features/housekeeping/domains/people/queries/people-adapters-production.test.ts
Test Files 4 passed (4)
Tests 40 passed (40)
pnpm exec vitest run --coverage.enabled=false src/features/housekeeping/domains/people src/features/housekeeping/foundation/foundation-source-contract.test.ts src/features/housekeeping/foundation/authorization.test.ts src/features/housekeeping/foundation/capability-context.test.ts src/features/housekeeping/foundation/server-capability-context.test.ts src/features/housekeeping/foundation/contracts/contracts.test.ts
Test Files 9 passed (9)
Tests 86 passed (86)
pnpm test:housekeeping
Test Files 49 passed (49)
Tests 435 passed (435)
pnpm typecheck
tsc --noEmit
Exit 0
pnpm exec biome check --formatter-enabled=false <7 exact changed Task 11 TypeScript files>
Checked 7 files. No fixes applied.
git diff --check
Exit 0
Both pnpm test:housekeeping and pnpm typecheck emitted the environment warning: the repository requires Node >=26.8.1 <27, while this host runs Node v26.7.0 with pnpm 11.24.0. Tests and typecheck still exited successfully.
No database operation, deployment, push, or pull-request update was performed.