Skip to content

Consent UI + follow-ups for consent-gated logging #503

Description

@kcarnold

Context

We added consent-gated logging to the production add-in so the privacy policy and
the code agree: each user has a loggingConsent level (none / usage /
ai_output / document) stored on their Better Auth record, and logging/analytics
are stripped to that level on both client and server. Logs are keyed by Better Auth
user id (the URL username is gone), and there's a deletion path.

The backend, client gating, and the rewritten privacy policy landed in #504. A
follow-on branch (feat/llm-usage-metering) then added per-user LLM cost metering
and reworked the deletion paths — see "What changed since this issue was written"
below. This issue tracks the remaining work: the consent UI.

Remaining: consent UI

  • Onboarding consent step. Default is strictly content-free; actively ask
    the user to choose their level. Choice architecture (no dark patterns):
    privacy-protective option pre-selected, equal visual weight, plain-language
    descriptions (see CONSENT_LEVEL_LABELS in frontend/src/consent.ts), content
    logging (ai_output/document) never pre-checked, and the longitudinal-study
    opt-in presented as a separate explicit consent.

  • Standalone authenticated account/consent page. Must be reachable without
    the Word task pane loaded — a new Vite entry (e.g. account.html / consent.html,
    alongside taskpane.html/editor.html/logs.html) or surfaced within
    editor.html. Gated on a Better Auth session (Bearer via authTokenStore);
    unauthenticated visitors go through sign-in first. Lets the user:

    • view and change their logging level -> POST /api/me/consent { level }
    • erase their logged activity -> DELETE /api/me/activity (withdrawal: study
      logs + analytics profile are erased, the account stays, LLM usage records keep
      accruing because the account is still billable)
    • delete their account -> Better Auth delete-user route (departure: the same
      erasure, plus the account itself; usage records are anonymized, not deleted)

    The two are deliberately different actions and the UI should say so — see the
    "Your Rights and Choices" section of the privacy policy for the user-facing wording.

What changed since this issue was written (feat/llm-usage-metering)

  • DELETE /api/me/data is now DELETE /api/me/activity. The old name promised
    more than it delivered (it never touched the account or the usage records).
  • Both deletion paths now route through one eraseLoggedData() (backend/src/erasure.ts),
    so account deletion is by construction a superset of withdrawal. It previously
    forgot to purge the PostHog person.
  • The auth DB is now the shared app.db (Better Auth's tables + ours, one
    connection), with our own PRAGMA user_version migrations in backend/src/db.ts.
  • Run npx @better-auth/cli migrate and wire it into the deploy repodone,
    via a different mechanism (backend/src/migrate.ts applies both schemas; the
    legacy auth.db is renamed on first open).
  • Verify deletePosthogPerson against our PostHog version — moved to Post-deploy verification: usage metering + data erasure #517
    (post-deploy verification), where it belongs: it can only be checked against a
    deployed environment, and it's now load-bearing for both deletion paths.

Decisions still open

  • none behavior: currently fully silent (PostHog opt-out also suppresses
    capture_exceptions). Decide whether we want an "errors-only at none" carve-out
    (needs autocapture-off + manual exception capture rather than opt-out).
  • EU / ePrivacy: confirm the onboarding prompt satisfies EU opt-in expectations
    for non-essential analytics (or add a region-aware consent banner).
  • Retention window: the policy says "while your account is active / until you
    request deletion". Now there are two categories to decide on: logged study
    events, and the content-free AI usage records (currently retained indefinitely
    as billing history). Pick concrete caps if we want them.
  • Fail-closed in prod: with auth disabled, the proxy still serves requests on
    the main key (metered as anonymous) rather than refusing. That keeps local dev
    working, but means a prod deploy that forgot BETTER_AUTH_ENABLED=true would
    quietly spend the main key unattributed. Consider keying the fallback on
    NODE_ENV instead.

Related

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions