Skip to content

Consent-gated logging + accurate privacy policy - #504

Merged
kcarnold merged 15 commits into
mainfrom
privacy
Jul 17, 2026
Merged

Consent-gated logging + accurate privacy policy#504
kcarnold merged 15 commits into
mainfrom
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 Outdated
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 Outdated
…tus check

- consent.ts: fix 'No logging' label to match actual fully-silent behavior
  (opt-out also suppresses crash reports), instead of promising crash reports
- app/index.tsx: only opt PostHog in when a stable user id is present, so we
  never capture anonymous events with no deletable identity
- posthog.ts: check the delete response status so a failed person deletion is
  reported, not silently treated as success
- logging.ts: rename deleteUserLogs param username -> userId for clarity

Skipped Copilot's modesToShow dependency comment: modesToShow aliases a
module-level constant (no stale-value risk) and biome flags including it as an
unnecessary dependency, so adding it would fail the repo's configured linter.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Base automatically changed from single-container-consolidation to main July 3, 2026 15:16
kcarnold added 4 commits July 16, 2026 16:16
- Rename auth.db -> app.db, containing both auth and usage logs
- Low-tech migrations using PRAGMA user_version
- Single db connection in db.ts, shared with better auth
- an llm_usage table with token usage by model, time-to-first-token, and total duration
- OpenAI API pricing table
The OpenAI proxy meters LLM usage to the appropriate category: the demo key, or the main app key. It rejects unauth non-demo requests.

Unfortunately, we need to scan for usage events in the SSE stream, and drain canceled streams to get the usage info in the last frame. This is a bit messy.
kcarnold added 8 commits July 17, 2026 10:01
…mo mode.

Demo mode signed in with a fake token, so /api/log had no identity to key on
and demo spend had no owner. Demo users now get a real Better Auth anonymous
session, so logging and usage attach to a real (if anonymous) user.

- Anonymous users are created at full logging consent, so all demo use is observable.
- LLM usage is metered per anonymous user (on the capped demo key), so demo spend
  can be capped per user later.
- Google Docs moves off demo mode onto the same device-auth session as Word.
- The demo editor discloses that all usage is logged and may be unavailable at peak.
@kcarnold
kcarnold merged commit 0c1085b into main Jul 17, 2026
8 of 9 checks passed
@kcarnold
kcarnold deleted the privacy branch July 17, 2026 17:57
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