3a44a1deedbf6e3d529617d96862018d1c548a6f
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3a44a1deed |
feat(portal-bff): audit log foundation per ADR-0013
Lays down the append-only audit log: schema, migration with role grants, NestJS AuditWriter service. Typed event-family methods, separate AUDIT_DATABASE_URL pool, retention job, and live-DB integration tests are explicitly listed as "wired as features land" in ADR-0013 §Confirmation. Schema (apps/portal-bff/prisma/schema.prisma): - multiSchema preview enabled; datasource declares public + audit schemas. - AuditEvent model with: id (uuid), createdAt, eventType (free-form in v1, will formalise into a catalogue once we have N stable types), audience (workforce|customer enum), actorIdHash, traceId, subject, outcome (success|failure|denied enum), payload (jsonb). - Indexes on createdAt, eventType, traceId — covering the obvious query shapes (date-range scan, by event family, by trace for log-audit correlation). Migration (prisma/migrations/*_init_audit_schema/migration.sql): - CREATE TABLE / enums via Prisma's standard output. - Append-only contract re-applied explicitly: ALTER TABLE / TYPE OWNER TO audit_owner, then GRANT INSERT to audit_writer, SELECT to audit_reader, SELECT+DELETE to audit_archiver. SELECT is required for archiver because Postgres needs SELECT on every column referenced in DELETE's WHERE clause to evaluate "older than retention" — granted explicitly in this PR rather than silently failing later. - USAGE on the enum types granted to all three roles so audit_ writer's INSERT does not fail with "permission denied for type". - No GRANT for UPDATE / TRUNCATE to anyone, including audit_owner at runtime — only fresh schema migrations amend the table. Service (apps/portal-bff/src/audit/): - AuditWriter.recordEvent(input) — single entry point. Wraps every INSERT in a transaction whose first statement is `SET LOCAL ROLE audit_writer`, so the role contract holds at runtime even from the otherwise-privileged BFF connection. SET LOCAL is reset on COMMIT/ROLLBACK so the pool's next consumer sees the original role. - traceId auto-resolved from the active OTel span context. - actorIdHash auto-resolved from CLS (key 'actorIdHash') with explicit input-side override; null when neither is set (placeholder until ADR-0009 / ADR-0010 guards populate CLS). - Errors propagate (no catch-and-swallow), per ADR-0013's "blocking writes: no audit ⇒ no action". - AuditModule exposes the writer; wired into AppModule so any future feature module can inject it. Tests (apps/portal-bff/src/audit/audit.service.spec.ts, 8 cases): - Locks the transaction to audit_writer before INSERTing. - Passes input fields through. - Records Prisma.JsonNull when no payload is provided. - Reads actorIdHash from CLS when not passed. - Prefers explicit actorIdHash over CLS-resolved. - Stores actorIdHash = null when neither input nor CLS has one. - Captures the active OTel trace id. - Stores traceId = null when no span is active. - Propagates the underlying error (no swallow). End-to-end smoke against local-dev Postgres (manual, via psql): - INSERT under audit_writer: ok. - UPDATE under audit_writer: "permission denied for table events". - DELETE under audit_writer: "permission denied for table events". - DELETE under audit_archiver (after the SELECT grant fix): ok, row removed. ADR-0013 §Confirmation rewritten as "wired in foundation PR" / "wired as features land", with the latter listing the typed event-family methods, AUDIT_DATABASE_URL split, startup self-test probe, retention purge job, salt-shared cross-correlation test, and live-DB role-contract integration tests. |
||
|
|
a0f6e594ba |
fix(portal-bff): downgrade Prisma to 6.x for nestjs-prisma compatibility (#3)
## Summary
- Pin `prisma` and `@prisma/client` to `^6` (resolved 6.19.3) instead of the previous `^7`.
- Switch the generator in `apps/portal-bff/prisma/schema.prisma` from `prisma-client` to `prisma-client-js` (Prisma 6's default).
- Add the explicit `url = env("DATABASE_URL")` in the `datasource db` block (required by Prisma 6; was implicit in Prisma 7 via `prisma.config.ts`).
- Remove `apps/portal-bff/prisma.config.ts` (Prisma 7-only feature).
- Drop the now-irrelevant `/generated/prisma` entry from `apps/portal-bff/.gitignore`.
## Motivation
Two distinct Prisma 7 breaking changes surfaced as runtime errors in `pnpm nx serve portal-bff`:
1. **Generator output path:** Prisma 7's default `prisma-client` generator writes to a custom output dir declared in the schema. `@prisma/client/default.js`'s runtime stub still resolves `.prisma/client/default` in a sibling `node_modules/.prisma/client/`, which only the legacy `prisma-client-js` generator populates. Result: `ESM loader error: Cannot find module '.prisma/client/default'`.
2. **PrismaClientOptions API:** In Prisma 7, `PrismaClientOptions` no longer exposes `datasourceUrl` nor `datasources`. The connection must come through a driver adapter (e.g. `@prisma/adapter-pg`). `nestjs-prisma@0.27.0` calls `super(undefined)` when no `prismaServiceOptions` is passed, which Prisma 7 rejects with `PrismaClientInitializationError: PrismaClient needs to be constructed with a non-empty, valid PrismaClientOptions`.
Both issues are downstream of Prisma 7's "adapter-first" architecture being incompatible with `nestjs-prisma`'s still-Prisma-6-shaped wrapper. Working around each issue separately would lead into bespoke wiring (custom PrismaService, manual `@prisma/adapter-pg` install, hand-rolled DI). That's bricolage on a foundational layer.
Per CLAUDE.md ("default to stable, recognized, battle-tested choices; cutting-edge alternatives only when the trade-off is captured in an ADR"), the cheapest, cleanest fix is to use Prisma 6 — still actively maintained, fully aligned with `nestjs-prisma`'s design, supported by every tutorial and example in the wider ecosystem.
## Implementation notes
- ADR-0006 ("Persistence — PostgreSQL with Prisma") specifies "Prisma" without pinning a version. The version choice is a tactical detail. No ADR amendment.
- `apps/portal-bff/.env.example` is unchanged — the `DATABASE_URL` variable name is the same in Prisma 6 and 7.
- `prisma.config.ts` removed: it was a Prisma 7 file that Prisma 6 ignores; keeping it would only confuse a future contributor.
- The Prisma 7 `prisma-client` generator left a residual output dir at `apps/portal-bff/generated/` from the previous setup; removed locally and excluded from gitignore (no longer needed).
- Re-evaluate when `nestjs-prisma` releases an update aligned with Prisma 7's adapter model. The path back is symmetric: pin Prisma to `^7`, restore the schema's `prisma-client` generator, install `@prisma/adapter-pg`, and update `app.module.ts` to instantiate the adapter.
## Verification
- [x] `pnpm exec prisma generate` populates `node_modules/.../@prisma+client@6.19.3/.prisma/client/default.js` (no more 7.x in the active resolution).
- [x] `pnpm nx build portal-bff` green.
- [X] `pnpm nx serve portal-bff` boots cleanly (no `PrismaClientInitializationError`) — to be confirmed locally before merge. Connection to a real Postgres is out of scope of this PR; if Postgres is not running, an `ECONNREFUSED` is expected and unrelated.
- [ ] `pnpm ci:check` — runs in CI on PR open.
## Related
- [ADR-0006 — Persistence: PostgreSQL with Prisma](docs/decisions/0006-persistence-postgresql-prisma.md). Generator and version are tactical; no amendment.
- Future: revisit Prisma version when `nestjs-prisma` ships an update with first-class driver-adapter support.
---------
Co-authored-by: Julien Gautier <julien.gautier@apf.asso.fr>
Reviewed-on: #3
|
||
|
|
2b0e20bd85 |
chore: wire PostgreSQL + Prisma per ADR-0006
Add Prisma 7 + nestjs-prisma. The schema lives at
apps/portal-bff/prisma/schema.prisma with provider postgresql; the new
prisma-client generator (Prisma 7 default) outputs the typed client to
apps/portal-bff/generated/prisma/ which is gitignored.
apps/portal-bff/src/app/app.module.ts imports PrismaModule.forRoot
({ isGlobal: true }) so PrismaService is injectable across the BFF
without per-module imports.
apps/portal-bff/.env.example documents DATABASE_URL with a local-dev
default, plus a forward list of env vars introduced by upcoming phases
and ADRs (auth, sessions, MFA, observability, audit, downstream APIs)
- catalog reference, not implementation. The actual .env stays
gitignored at both repo root and app levels.
prisma.config.ts (Prisma 7's TypeScript config) is committed; it loads
DATABASE_URL via dotenv. Schema and migrations paths are pinned to
prisma/ relative to the bff app.
PostgreSQL provisioning, RLS policies for the dual-audience design,
the dedicated audit schema with role grants (audit_owner / audit_writer
/ audit_reader / audit_archiver per ADR-0013), and column-level
encryption for L3-scoped data are out of scope of this commit -
they belong with the future on-prem infrastructure ADR.
|