Skip to content

fix(gdpr): include system_logs/api_metrics/error_logs in DSAR export (#545) - #1880

Merged
2witstudios merged 4 commits into
masterfrom
pu/gdpr-545-dsar-export
Jul 6, 2026
Merged

2witstudios merged 4 commits into
masterfrom
pu/gdpr-545-dsar-export

Conversation

@2witstudios

@2witstudios 2witstudios commented Jul 6, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Issue [Compliance] DSAR controls #545 flagged that a data subject's Art 15 access/export request omits system_logs, api_metrics, and error_logs, even though those tables retain a nullable, no-FK user_id column right up until account deletion.
  • Note on scope: issue [Compliance] DSAR controls #545's full ask is broader (a log-governance matrix across activity/security_audit/system_logs/api_metrics/error_logs/ai_usage, plus data-residency/egress policy). This PR is a scoped slice covering only the confirmed DSAR export gap for the three named monitoring tables; the issue is intentionally left open pending the rest of that broader work, which is out of scope here. See the issue comment for two follow-ups I found but deliberately did not fold into this PR: security_audit_log is also still missing from the export, and the unbounded (no-LIMIT) query pattern this PR extends to system_logs/api_metrics could use pagination at scale (precedent: PR fix(gdpr): complete GDPR Art. 15 data export coverage #1084).
  • Deletion was already fully wired (packages/lib/src/logging/monitoring-purge.ts already purges all three tables on erasure) — the gap was entirely on the export side.
  • Added collectUserSystemLogs, collectUserApiMetrics, collectUserErrorLogs to packages/lib/src/compliance/export/gdpr-export.ts, mirroring collectUserActivity's existing pattern, and wired them into AllUserData/collectAllUserData.
  • Also updated packages/lib/src/compliance/export/export-format.ts — its buildNativeExportFiles and toPortableExport functions manually enumerate every AllUserData field (not derived from Object.keys), so without this change the new data would be collected from the DB but silently dropped from the actual ZIP/portable export a user downloads.
  • Updated docs/security/gdpr-export-format.md and CHANGELOG.md (per repo CLAUDE.md's changelog rule for user-visible changes) to document the three new native files and portable additionalProperty entries.

Redaction judgment call

Each new table carries internal diagnostic/security telemetry alongside the fields relevant to a data subject: ip, userAgent, errorStack/stack, sessionId, requestId, opaque metadata jsonb, and (for error_logs) resolvedBy (an internal admin's user id, not the subject's).

I chose to export only the fields describing what happened, when, and where:

  • UserSystemLogExport: id, timestamp, level, message, category, endpoint, method, duration
  • UserApiMetricExport: id, timestamp, endpoint, method, statusCode, duration, requestSize, responseSize
  • UserErrorLogExport: id, timestamp, name, message, endpoint, method, file, line, column, resolved

Excluded: raw stack traces, IP addresses, user agents, session/request correlation IDs, opaque metadata blobs, and internal admin references. This is a deliberate exclusion, not an oversight. Happy to revisit if legal/compliance wants stack traces or IPs included for completeness.

Test plan

  • bun test (vitest) for packages/lib/src/compliance/export/ — 48/48 new+existing tests pass (35 in gdpr-export.test.ts, 13 in export-format.test.ts, including null-path coverage for every nullable field on the 3 new collectors), 217+/217+ across packages/lib/src/compliance pass
  • bunx tsc --noEmit clean for packages/lib, apps/web, apps/admin
  • Confirmed apps/web/.../account/export/route.ts and apps/admin/.../export/route.ts need no changes (thin delegator + object-spread, respectively) — verified by reading both
  • apps/web/src/app/api/account/export/__tests__/route.test.ts — its mockUserData fixture was missing the 3 new required AllUserData fields, which made buildNativeExportFiles/toPortableExport throw a runtime TypeError once called with a stale fixture; fixed the fixture, updated two hardcoded archive.append() call-count assertions (+3), and added presence assertions for the 3 new files. All 17 tests in this file now pass, verified via the actual CI command (bun run test:coverage) after a full monorepo rebuild.
  • Independent 8-angle review pass (correctness, removed-behavior, cross-file breakage, reuse, simplification, efficiency, altitude, CLAUDE.md conventions) surfaced only: a missing changelog entry (fixed), thin null-path test coverage (fixed), and a documented-but-not-fixed positional-destructuring readability risk in collectAllUserData (added a one-line invariant comment) — see commit history for details.

Refs #545

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

…545)

DSAR export via collectAllUserData never touched the systemLogs,
apiMetrics, and errorLogs monitoring tables, even though they retain a
nullable, no-FK userId column right up until account deletion (deletion
side was already covered by monitoring-purge.ts). Add three collectors
mirroring collectUserActivity's pattern, wire them into AllUserData,
and thread the new fields through both export-format.ts serializers
(buildNativeExportFiles and toPortableExport enumerate AllUserData
fields explicitly rather than deriving from Object.keys, so both had
to be updated or the new data would be collected but silently dropped
from the actual downloadable export).

Redaction judgment call: each new *Export interface keeps only the
what/when/where fields (timestamp, level/name, message, category,
endpoint, method, duration/status, file/line/column, resolved) and
excludes raw stack traces, IP addresses, user agents, session/request
correlation IDs, opaque metadata blobs, and internal admin references
(errorLogs.resolvedBy) — internal diagnostic/security telemetry that
isn't data about the subject's own activity.

Updated docs/security/gdpr-export-format.md to document the three new
native files and portable additionalProperty entries.

Refs #545
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Jul 6, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@2witstudios, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 26 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 726606f5-9c8e-4d67-90ee-8084c5f2dc19

📥 Commits

Reviewing files that changed from the base of the PR and between 7761f3e and 94d804a.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • packages/lib/src/compliance/export/export-format.test.ts
  • packages/lib/src/compliance/export/gdpr-export.test.ts
  • packages/lib/src/compliance/export/gdpr-export.ts
📝 Walkthrough

Walkthrough

This PR extends the GDPR user data export system to include three new monitoring categories: system logs, API metrics, and error logs. New collector functions and export interfaces are added to gdpr-export.ts, wired into native/portable export formats, surfaced in the account export API route, and documented, with corresponding test coverage updated throughout.

Changes

Monitoring data in GDPR export

Layer / File(s) Summary
GDPR collector functions and data model
packages/lib/src/compliance/export/gdpr-export.ts, packages/lib/src/compliance/export/gdpr-export.test.ts
New UserSystemLogExport, UserApiMetricExport, UserErrorLogExport interfaces and collectUserSystemLogs, collectUserApiMetrics, collectUserErrorLogs collector functions are added; AllUserData and collectAllUserData are extended to include and aggregate the new categories, with new tests validating collectors and aggregation.
Native and portable export format wiring
packages/lib/src/compliance/export/export-format.ts, packages/lib/src/compliance/export/export-format.test.ts
buildNativeExportFiles adds system-logs.json, api-metrics.json, error-logs.json bundle entries; toPortableExport adds corresponding additionalProperty entries; tests verify record counts and lossless portable representation.
Export route inventory and documentation
apps/web/src/app/api/account/export/__tests__/route.test.ts, docs/security/gdpr-export-format.md
Route test fixtures and archive-content assertions are updated to include the new files/categories; documentation describes the expanded native and portable format inventories.

Estimated code review effort: 2 (Simple) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ExportRoute
  participant collectAllUserData
  participant Database
  participant ExportFormat

  Client->>ExportRoute: GET /api/account/export
  ExportRoute->>collectAllUserData: fetch user data(userId)
  collectAllUserData->>Database: query systemLogs, apiMetrics, errorLogs, others
  Database-->>collectAllUserData: rows
  collectAllUserData-->>ExportRoute: AllUserData
  ExportRoute->>ExportFormat: buildNativeExportFiles(AllUserData)
  ExportFormat-->>ExportRoute: system-logs.json, api-metrics.json, error-logs.json, others
  ExportRoute-->>Client: ZIP archive
Loading

Related Issues: None specified.

Related PRs: None specified.

Suggested labels: compliance, gdpr, monitoring

Suggested reviewers: None specified.

🐰 A hop through logs both new and old,
Metrics and errors, neatly told,
Zipped up tight in JSON's embrace,
Every trace now has its place.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the three monitoring exports to the GDPR/DSAR export flow.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pu/gdpr-545-dsar-export

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

mockUserData was missing systemLogs/apiMetrics/errorLogs, which are now
required AllUserData fields. Since export-format.ts's
buildNativeExportFiles/toPortableExport unconditionally read
data.systemLogs.length etc., calling them with the stale fixture threw
a runtime TypeError, failing CI (ci / Unit Tests). Also bump the two
hardcoded archive.append() call-count assertions by 3 for the new
system-logs.json/api-metrics.json/error-logs.json files, and assert
their presence alongside the other category assertions.

My earlier local verification of this file was invalid: I git-stash'd
the source changes but never rebuilt packages/lib's dist output, so
apps/web (which imports the built package) was still running against
the new compiled code with the old test fixture, making the "before"
comparison meaningless. Verified this time via the actual CI command
(bun run test:coverage) after a full rebuild.

Refs #545
…note

- Add a CHANGELOG.md Fixed entry for the expanded DSAR export (repo
  CLAUDE.md requires changelog updates for user-visible changes).
- Add null-value coverage for the nullable fields on the 3 new
  collectors/interfaces (systemLogs.category/endpoint/method/duration,
  apiMetrics.requestSize/responseSize, errorLogs.resolved/endpoint/
  file/line/column), matching the existing null-path testing bar used
  elsewhere in this file (collectUserActivity's metadata: null, etc.).
  Also swap export-format.test.ts's lossless-fixture entries for these
  3 categories to exercise null fields, matching how the other
  categories in that same fixture already do.
- Add a one-line comment above collectAllUserData's Promise.all noting
  the array/destructuring order must stay in lockstep — this tuple grew
  from 10 to 13 positions in this PR with no compiler-checkable safety
  net against a future reorder mismatch.

Refs #545
@2witstudios
2witstudios merged commit 0146527 into master Jul 6, 2026
3 checks passed
@2witstudios 2witstudios mentioned this pull request Jul 6, 2026
4 tasks
@2witstudios
2witstudios deleted the pu/gdpr-545-dsar-export branch July 6, 2026 04:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant