707ff25ee007bd9f50c99e00aa7df019208f359c
First real consumer of the admin module — paginated audit-log viewer
per ADR-0020's v1 catalogue, gated by @RequireAdmin and emitting
admin.audit.query on every read as the "fishing expedition" deterrent
called out in ADR-0020 §"Audit log captures … Read actions are also
captured … to deter fishing expeditions".
What ships
- AuditReader (apps/portal-bff/src/admin/audit-reader.service.ts):
- SET LOCAL ROLE audit_reader inside the transaction — symmetric
with AuditWriter's SET LOCAL ROLE audit_writer. The runtime
role contract holds even when the BFF connection is otherwise
privileged; UPDATE/INSERT/DELETE on audit.events fail at the
Postgres level.
- Parameterised SELECT only — never concatenates filter values
into SQL. Subject prefix uses LIKE with explicit ESCAPE and
escapes %/_ in the literal so an admin-side wildcard can't
masquerade as a meta-character.
- COUNT(*) + LIMIT/OFFSET pagination. Order: created_at DESC,
id DESC for deterministic page boundaries on identical
timestamps.
- Hard caps limit at MAX_LIMIT (200) even if a caller bypasses
the DTO — defense in depth on the BFF's event loop.
- AdminAuditQueryDto (apps/portal-bff/src/admin/audit-query.dto.ts):
- Filters: eventType, actorIdHash, audience, outcome,
subjectPrefix, createdAtFrom/createdAtTo (ISO-8601).
- Pagination: limit (1..200, default 50), offset (default 0).
- Wired through Nest's global ValidationPipe so unknown query keys
are rejected (forbidNonWhitelisted) and limit is bounded.
- AdminAuditController:
- GET /api/admin/audit, @RequireAdmin at the class level.
- Forwards the validated DTO to AuditReader, then emits
admin.audit.query with { filters, resultCount } as payload so a
reviewer can see what the admin searched for.
- @RequireMfa intentionally NOT applied in v1: the admin surface
already sits behind a freshly-MFA'd session at sign-in, and the
per-query audit row is the deterrent. Adding @RequireMfa later
is a one-line change.
- AuditWriter gains adminAuditQuery() typed method emitting the
event with outcome=success (the read happened; whether it
returned rows is captured separately in resultCount).
Tests: +30 specs (AuditReader 10, AdminAuditController 6, AuditWriter
typed event 2, DTO validation 12). All 308 BFF specs pass.
Documentation index
This is the entry point to all project documentation. It is maintained automatically: any addition, rename, or removal of a .md file under docs/ must be reflected here in the same change.
Conventions
- Documentation is written in English.
- One topic per file. Group related files into a folder when there are three or more.
- Cross-reference with relative links so they keep working in GitHub, IDEs, and exported sites.
- For architectural decisions, do not add them here — they belong in decisions/ as MADR 4.0.0 ADRs.
Sections
Daily development
- development.md — repo layout, prerequisites, initial setup, daily commands, observability dev-loop (Jaeger UI, log ↔ trace correlation), dependency updates (Renovate), conventional commit cycle. Day-to-day reference for working on the project.
Architecture
- architecture.md — cross-cutting Mermaid diagrams: C4 system context, C4 containers, Nx module boundaries, CI/CD pipeline. Single-decision diagrams (auth sequence, ERD, etc.) live inline in their ADR.
Onboarding & environment
Setup guides for new contributors:
- setup/01-wsl-terminal-setup.md — modern WSL terminal (Zsh + Powerlevel10k + CLI tools)
- setup/02-dev-web-stack.md — Node via nvm, pnpm via corepack, Docker
- setup/03-angular-nx-monorepo.md — Angular + Nx monorepo bootstrap
Operations & runbooks
Empty — to be populated when we deploy.
Security, performance, accessibility
Empty — placeholders to be filled with rationale docs alongside their corresponding ADRs.
Description
Languages
TypeScript
85.3%
JavaScript
5.4%
SCSS
4.3%
HTML
3.9%
Shell
0.8%
Other
0.3%