feat(gdpr): author erasure (PR5 of #6701)#7550
Conversation
Review Summary by Qodo(Agentic_describe updated until commit 16cd84a)GDPR author erasure with resumable partial-failure recovery
WalkthroughsDescription• Implement GDPR Art. 17 right-to-erasure via anonymizeAuthor(authorID) function - Zeroes display identity (name, colorId) on globalAuthor:<id> record - Deletes all token2author:* and mapper2author:* bindings pointing to author - Nulls authorId on chat messages authored by the person - Preserves pad content, revisions, and attribute pools intact • Add REST endpoint POST /api/1.3.1/anonymizeAuthor?authorID=… with admin auth • Implement idempotent erasure with resumable partial-failure recovery • Add comprehensive unit and integration tests plus privacy documentation Diagramflowchart LR
A["Author Request<br/>anonymizeAuthor"] --> B["Drop token2author<br/>& mapper2author"]
B --> C["Zero display identity<br/>name & colorId"]
C --> D["Scrub chat messages<br/>null authorId"]
D --> E["Set erased=true<br/>sentinel"]
E --> F["Return counters<br/>& idempotent"]
G["Partial Failure"] -.->|Resume| B
File Changes1. src/node/db/AuthorManager.ts
|
Code Review by Qodo
1. Legacy chat IDs not cleared
|
| version['1.3.1'] = { | ||
| ...version['1.3.0'], | ||
| anonymizeAuthor: ['authorID'], | ||
| }; | ||
|
|
||
| // set the latest available API version here | ||
| exports.latestApiVersion = '1.3.0'; | ||
| exports.latestApiVersion = '1.3.1'; |
There was a problem hiding this comment.
1. anonymizeauthor lacks feature flag 📘 Rule violation ☼ Reliability
The new anonymizeAuthor REST/API surface is registered unconditionally and becomes available by default, without any enable/disable mechanism. This violates the requirement that new features be gated behind a feature flag and disabled by default.
Agent Prompt
## Issue description
A new feature (`anonymizeAuthor` API/REST endpoint) is enabled by default and has no feature-flag gating.
## Issue Context
Compliance requires new features to be behind a feature flag and disabled by default.
## Fix Focus Areas
- src/node/handler/APIHandler.ts[146-152]
- src/node/db/API.ts[65-77]
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Qodo review: the `erased: true` sentinel was written before the chat scrub loop, so a throw during scrub left chat messages untouched while subsequent calls short-circuited on `existing.erased` and never finished. Split the write: zero the display identity first (still hides the name), run the chat scrub, and only then stamp `erased: true` so a retry resumes the sweep. Regression test covers the partial-run → retry path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ⓘ You've reached your Qodo monthly free-tier limit. Reviews pause until next month — upgrade your plan to continue now, or link your paid account if you already have one. |
|
Persistent review updated to latest commit 16cd84a |
| for (let i = 0; i <= chatHead; i++) { | ||
| const chatKey = `pad:${padID}:chat:${i}`; | ||
| const msg = await db.get(chatKey); | ||
| if (msg != null && msg.authorId === authorID) { | ||
| msg.authorId = null; | ||
| await db.set(chatKey, msg); | ||
| clearedChatMessages++; | ||
| } |
There was a problem hiding this comment.
1. Legacy chat ids not cleared 🐞 Bug ≡ Correctness
AuthorManager.anonymizeAuthor() only nulls msg.authorId, so chat records stored with legacy userId (explicitly supported as an older DB shape) will not be anonymized and will keep referencing the erased authorID.
Agent Prompt
### Issue description
`src/node/db/AuthorManager.anonymizeAuthor()` only detects chat messages authored by the target author via `msg.authorId`, but chat records might use the legacy field names (`userId`/`userName`). Those legacy records will not be scrubbed, leaving author linkage behind.
### Issue Context
`ChatMessage.fromObject()` explicitly supports old DB records where `userId` was renamed to `authorId` and `userName` to `displayName`.
### Fix Focus Areas
- src/node/db/AuthorManager.ts[388-395]
### Implementation notes
- When loading `msg` from `pad:<padId>:chat:<i>`, treat the author field as `msg.authorId ?? msg.userId`.
- If it matches `authorID`, clear **both** `authorId` and `userId` (and consider clearing `displayName`/`userName` if present) before writing back.
- Keep the stored value a plain object (avoid storing a `ChatMessage` instance that could serialize differently).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
* Resolve doc/privacy.md conflict by folding the GDPR Art. 17 section into develop's expanded privacy doc (kept IP/banner content). * Consolidate the duplicated `version['1.3.1']` declaration in APIHandler.ts so both compactPad (from develop) and anonymizeAuthor (from this branch) live in one map. * Address Qodo rule violation: gate anonymizeAuthor on a new `gdprAuthorErasure.enabled` setting (default false). API.ts now rejects calls with an apierror when disabled; settings.json.template and settings.json.docker document the toggle. Integration test flips the flag in `before()` and asserts the disabled-flag error path. * Qodo bug 'non-resumable partial erasure' was already fixed by 16cd84a — `erased: true` is stamped only after the chat scrub loop completes, so a thrown scrub now resumes on retry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
authorManager.anonymizeAuthor(authorID)zeroes the display identity onglobalAuthor:<id>(keeps the record as an opaque stub so existing changeset references still resolve), deletes everytoken2author:*andmapper2author:*binding that points at the author, and nullsauthorIdon chat messages they posted. Pad content, revisions, and attribute pool are kept intact.POST /api/1.3.1/anonymizeAuthor?authorID=…— admin-auth via the existing apikey/JWT pipeline.doc/privacy.mdexplains exactly what the call does and does not do.Final PR of the #6701 GDPR work. PR1 #7546 (deletion controls), PR2 #7547 (IP/privacy audit), PR3 #7548 (HttpOnly author cookie), PR4 #7549 (privacy banner) complete the set.
Design:
docs/superpowers/specs/2026-04-19-gdpr-pr5-author-erasure-design.mdPlan:
docs/superpowers/plans/2026-04-19-gdpr-pr5-author-erasure.mdTest plan