Skip to content

Consent-gated logging + accurate privacy policy#504

Draft
kcarnold wants to merge 1 commit into
single-container-consolidationfrom
privacy
Draft

Consent-gated logging + accurate privacy policy#504
kcarnold wants to merge 1 commit into
single-container-consolidationfrom
privacy

Conversation

@kcarnold

Copy link
Copy Markdown
Contributor

What & why

Reconciles the production add-in's data practices with its privacy policy. Previously
the policy promised document content was stored only on research opt-in, while the code
logged full document text + AI results to plaintext JSONL for every user with no consent
gate, OpenAI wasn't disclosed, and there was no deletion path.

This introduces a 4-level logging-consent model (none / usage / ai_output /
document) stored on the Better Auth user record, gates logging + analytics to that
level on both client and server, keys logs to a stable auth identity (so deletion is
possible), and rewrites the policy to match.

Note: the add-in is a product first, with a longitudinal-study opt-in on top. These
tiers govern what we store; OpenAI still processes document text to generate
suggestions regardless of level.

Backend

  • consent.ts — levels + authoritative content-stripping filter
  • auth.tsloggingConsent / consentUpdatedAt user fields (server-controlled,
    input: false); deleteUser enabled with a beforeDelete hook that purges logs
  • app.ts/api/log requires a session, keys by user id, enforces consent;
    new POST /api/me/consent + DELETE /api/me/data; /api/protected returns
    id + loggingConsent
  • logging.ts deleteUserLogs(); posthog.ts deletePosthogPerson() (best-effort)
  • Tests cover all four tiers, id-keying, username-spoof rejection, 401s, consent
    update, and deletion (25 passing)

Frontend

  • consent.ts mirrors the backend + UI labels; consent flows deviceAuth
    appAuthContext
  • useLog hook — consent-aware, token-authenticated logging; draft/index.tsx
    refactored to it; dead log() / usernameAtom / userContext.tsx removed
  • PostHog starts opt-out-by-default; PostHogConsentBridge opts in + identifys
    only at consent ≥ usage

Privacy policy

Rewritten: provider-flexible AI disclosure (OpenAI today), Google + PostHog listed,
Auth0 removed, four logging levels explained, account-associated + deletable framing,
deletion + retention, contact filled in.

Status

Backend + frontend typecheck; backend tests pass. Draft — the consent UI
(onboarding step + standalone account/consent page), the Better Auth migration, and a
few open decisions remain. Tracked in #503.

Base is single-container-consolidation (this branch builds on it) so the diff shows
only the consent work.

Each user gets a loggingConsent level (none/usage/ai_output/document) stored on
their Better Auth record. Logging and PostHog analytics are stripped to that level
on both client and server; logs are keyed by Better Auth user id (the URL username
is removed) and there is a deletion path.

Backend:
- consent.ts: 4-level model + authoritative content-stripping filter
- auth.ts: loggingConsent/consentUpdatedAt user fields (server-controlled) and
  deleteUser enabled with a beforeDelete hook that purges logs
- app.ts: /api/log requires a session, keys by user id, enforces consent;
  new POST /api/me/consent and DELETE /api/me/data; /api/protected returns
  id + loggingConsent
- logging.ts deleteUserLogs(); posthog.ts deletePosthogPerson() (best-effort)
- tests cover all tiers, id-keying, username-spoof rejection, 401s, deletion

Frontend:
- consent.ts mirrors the backend + UI labels; consent flows through
  deviceAuth -> appAuthContext
- useLog hook: consent-aware, token-authenticated logging; draft refactored to it;
  dead log()/usernameAtom/userContext removed
- PostHog starts opt-out-by-default; PostHogConsentBridge opts in + identifies
  only at consent >= usage

Privacy policy rewritten: provider-flexible AI disclosure (OpenAI today), Google +
PostHog listed, Auth0 removed, the four logging levels explained, account-associated
+ deletable framing, deletion + retention, contact filled in.

Remaining work (consent UI, migration, decisions) tracked in #503.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aligns the add-in’s logging/analytics behavior with a new, explicit 4-tier logging-consent model and updates the published privacy policy accordingly. It introduces consent-aware payload stripping on both client and server, switches log identity from client-supplied usernames to authenticated Better Auth user IDs (enabling deletion), and adds data-deletion endpoints plus PostHog opt-in behavior gated by consent.

Changes:

  • Added shared consent levels (none / usage / ai_output / document) with authoritative server-side stripping and mirrored client-side filtering.
  • Reworked event logging to require an authenticated session (Bearer token), key logs by stable user ID, and added endpoints to update consent and delete user data.
  • Updated analytics initialization to be opt-out-by-default and updated the privacy policy to reflect actual processing/storage and third-party disclosures.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
frontend/src/pages/draft/index.tsx Switches draft-page event logging to the new consent-aware useLog hook.
frontend/src/pages/app/index.tsx Makes PostHog opt-out-by-default and adds a consent→PostHog bridge for opt-in/identify behavior.
frontend/src/hooks/useLog.ts Adds authenticated, consent-aware client logging with payload filtering and Bearer token attachment.
frontend/src/contexts/userContext.tsx Removes legacy URL username identity source.
frontend/src/contexts/appAuthContext.tsx Extends auth session shape to include user.id and resolved loggingConsent.
frontend/src/consent.ts Adds client-side consent levels, UI labels, and payload filtering logic.
frontend/src/api/index.ts Removes legacy anonymous log() API and points to useLog.
frontend/src/api/deviceAuth.ts Extends /api/protected user shape to include id and loggingConsent.
frontend/public/privacypolicy.html Rewrites privacy policy to match consent-gated logging and provider disclosures.
backend/src/posthog.ts Adds best-effort deletion of a user’s PostHog person/events via management API (when configured).
backend/src/logging.ts Adds deleteUserLogs() to support “delete my data” and account deletion hooks.
backend/src/consent.ts Adds server-side consent-level ordering and authoritative content stripping.
backend/src/auth.ts Adds loggingConsent / consentUpdatedAt user fields and a delete-user hook to purge logs.
backend/src/app.ts Requires session for /api/log, gates content by consent, adds /api/me/consent and /api/me/data.
backend/src/tests/consent.test.ts Adds consent filtering tests for all tiers and ordering.
backend/src/tests/app.test.ts Adds tests for auth-required logging, ID-keying, consent gating, consent update, and deletion.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread frontend/src/consent.ts
Comment on lines +38 to +42
none: {
title: 'No logging',
description:
'No usage analytics or event logging. Only anonymous crash reports, to keep the app working.',
},
Comment on lines +413 to +419
const analyticsAllowed =
isAuthenticated && consentRank(loggingConsent) >= consentRank('usage');

if (analyticsAllowed) {
if (user?.id) posthog.identify(user.id);
posthog.opt_in_capturing();
} else {
Comment on lines 396 to 400
docContext: docContextRef.current,
});
getSuggestion(request, false);
}, [getFetcher, getSuggestion, modesToShow, shouldAutoRefresh, username]);
}, [getFetcher, getSuggestion, shouldAutoRefresh, log]);

Comment thread backend/src/posthog.ts
Comment on lines +52 to +55
await fetch(
`${host}/api/projects/${projectId}/persons/?distinct_id=${encodeURIComponent(distinctId)}&delete_events=true`,
{ method: 'DELETE', headers: { Authorization: `Bearer ${personalKey}` } },
);
Comment thread backend/src/logging.ts
Comment on lines +59 to +61
export async function deleteUserLogs(username: string): Promise<void> {
await rm(logFilePath(username), { force: true });
}
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.

2 participants