Files
EpicNext-Cms/.superpowers/sdd/2026-08-26-housekeeping-completion/task-11-report.md
T
Simo 7920d4f46c
CI / check (pull_request) Successful in 31s
CI / deploy (pull_request) Skipped
CI / release (pull_request) Skipped
fix(housekeeping): complete people read fidelity
2026-08-29 10:34:44 +02:00

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/people link 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-local ListInput and Page were 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_VIEW alone receives the safe base projection with both values set to null, 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 guild userId/roomId and CFH senderId/reportedId/roomId/moderatorId sentinels. Missing valid detail entities map to NOT_FOUND; invalid request identifiers map to VALIDATION.
  • Pagination clamps page size to 100 and offset to 10,000. Deterministic primary sorting, numeric-ID tie breaking, and LIMIT/OFFSET now execute in the database; no list query fetches a prefix for locale re-sorting or second slicing.
  • Raw production adapters and buildPeopleUserSelection are 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/tickets inbox 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 0 remains the permanent-active sentinel; expired rows cannot hide permanent or future-active bans in lists or details.

Official fix round 1 findings

  1. 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.
  2. Pagination and sorting: fixed by moving declared sort fields, deterministic tie ordering, and bounded LIMIT/OFFSET to production adapters. The exact user10/user2 and support status/updatedAt page regressions are covered.
  3. Resource bounds: fixed by replacing multi-account prefix loading plus per-row Promise.all with one bounded batched CTE/window query. No million-row prefix and no N+1 cluster query remain.
  4. 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, ID 0, or corrupt canonical links.
  5. Canonical dependencies: fixed watched and permission-rank data for user detail/edit; /support/tickets now calls strict fetchUnifiedTicketInbox; support queue/staff/active-ban data is explicit in canonical query DTOs.
  6. Moderation capability equality: fixed after direct user authorization. moderationQuery.capability now exactly equals the existing eleven-slug overview union already used by the route and run; no route, mutation, rank threshold, or new slug changed.
  7. Active bans: fixed list/detail selection so permanent ban_expire = 0 and 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

  1. Entity-aware sentinels: canonical guild DTOs now use the real schema names userId and roomId. 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 *Id zero values, negatives, unsafe integers, and primary ID zero remain unavailable failures.
  2. Support source fidelity: people.support.tickets remains on strict fetchUnifiedTicketInbox. people.support.ticket-desk now dispatches to a distinct strict website_tickets loader 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.
  3. 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.