Skip to content

feat(privacy): gate runs on AI opt in org setting#645

Open
sarahxsanders wants to merge 8 commits into
feat/privacy-disclosurefrom
feat/ai-opt-in-gate
Open

feat(privacy): gate runs on AI opt in org setting#645
sarahxsanders wants to merge 8 commits into
feat/privacy-disclosurefrom
feat/ai-opt-in-gate

Conversation

@sarahxsanders

@sarahxsanders sarahxsanders commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

review in graphite if easier (stacked): https://app.graphite.com/github/pr/PostHog/wizard/644/feat(privacy)-consolidate-disclosure-into-Privacy-%26-data-usage-panel

gates wizard runs on AI opt in org setting. there's three behaviors:

  • opted in already: wizard runs like normal
  • not opted in (admin perms): asked to enable the setting for third party providers (since we use claude) w/ easy open controls
  • no opted in (member perms): prompted to ask their org admin to turn it on

mirrors Max's strict reading from in-app: null, undefined, false all block. only literal true proceeds. we'll want to pay attention to usage, so I've added some tracking to this to see if we get spikes: https://us.posthog.com/project/2/dashboard/1701446

programs that don't run the agent (doctor, mcp add/remove/tutorial, upload-source-maps) opt out via requiresAi: false on ProgramConfig and skip the gate entirely!!!

CI

treated as implicit consent

demos

AI opt in screen (with admin perms):
CleanShot 2026-06-11 at 15 47 57@2x

AI opt in screen (w/o admin perms):
CleanShot 2026-06-11 at 15 48 34@2x

testing

pnpm try --playground and tab to AI opt-in (admin) / AI opt-in (non-admin)

Copy link
Copy Markdown
Collaborator Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@github-actions

Copy link
Copy Markdown

🧙 Wizard CI

Run the Wizard CI and test your changes against wizard-workbench example apps by replying with a GitHub comment using one of the following commands:

Test all apps:

  • /wizard-ci all

Test all apps in a directory:

  • /wizard-ci basic-integration
  • /wizard-ci error-tracking-upload-source-maps
  • /wizard-ci misc
  • /wizard-ci revenue

Test an individual app:

  • /wizard-ci basic-integration/android
  • /wizard-ci basic-integration/angular
  • /wizard-ci basic-integration/astro
Show more apps
  • /wizard-ci basic-integration/django
  • /wizard-ci basic-integration/fastapi
  • /wizard-ci basic-integration/flask
  • /wizard-ci basic-integration/javascript-node
  • /wizard-ci basic-integration/javascript-web
  • /wizard-ci basic-integration/laravel
  • /wizard-ci basic-integration/next-js
  • /wizard-ci basic-integration/nuxt
  • /wizard-ci basic-integration/python
  • /wizard-ci basic-integration/rails
  • /wizard-ci basic-integration/react-native
  • /wizard-ci basic-integration/react-router
  • /wizard-ci basic-integration/sveltekit
  • /wizard-ci basic-integration/swift
  • /wizard-ci basic-integration/tanstack-router
  • /wizard-ci basic-integration/tanstack-start
  • /wizard-ci basic-integration/vue
  • /wizard-ci error-tracking-upload-source-maps/android
  • /wizard-ci error-tracking-upload-source-maps/flutter
  • /wizard-ci error-tracking-upload-source-maps/ios
  • /wizard-ci error-tracking-upload-source-maps/next
  • /wizard-ci error-tracking-upload-source-maps/next-no-posthog
  • /wizard-ci error-tracking-upload-source-maps/node-raw
  • /wizard-ci error-tracking-upload-source-maps/node-rollup
  • /wizard-ci error-tracking-upload-source-maps/node-rollup-typescript-plugin
  • /wizard-ci error-tracking-upload-source-maps/node-webpack
  • /wizard-ci error-tracking-upload-source-maps/nuxt-3-6
  • /wizard-ci error-tracking-upload-source-maps/nuxt-4-3
  • /wizard-ci error-tracking-upload-source-maps/react-native
  • /wizard-ci error-tracking-upload-source-maps/react-vite
  • /wizard-ci error-tracking-upload-source-maps/rust
  • /wizard-ci misc/quack-quack
  • /wizard-ci revenue/stripe

Results will be posted here when complete.

@sarahxsanders sarahxsanders changed the base branch from feat/privacy-disclosure to graphite-base/645 June 11, 2026 17:22
@sarahxsanders sarahxsanders changed the base branch from graphite-base/645 to feat/privacy-disclosure June 11, 2026 17:22
@sarahxsanders sarahxsanders force-pushed the feat/ai-opt-in-gate branch 7 times, most recently from eb765e7 to ec2e602 Compare June 11, 2026 19:43
@sarahxsanders sarahxsanders changed the title feat(privacy): gate runs on organization is_ai_data_processing_approved feat(privacy): gate runs on AI opt in org setting Jun 11, 2026
@sarahxsanders sarahxsanders marked this pull request as ready for review June 11, 2026 20:51
@sarahxsanders sarahxsanders requested a review from a team June 11, 2026 20:52
sarahxsanders and others added 8 commits June 11, 2026 16:53
Honors the same AI opt-in toggle Max gates on. When the wizard
authenticates against an org whose is_ai_data_processing_approved is not
true, AiOptInRequiredScreen renders before the agent starts. Admins get
[O] open-settings; non-admins see a copyable link to share with their
admin. Both variants offer [S] BYOAI skill, [R] retry without
restarting, and [E] exit.

Gate plumbing: screen-sequences.ts injects an ai-opt-in ProgramStep
after the auth step for any program whose requiresAi !== false. Strict
reading matches Max (only literal true proceeds); apiUser=null is
treated as "fetch hasn't happened yet" to avoid flashing the gate
during the brief window between setCredentials and setApiUser emits.

Predicate coverage: tests in programs.test.ts cover all four field
states (true / false / null / undefined), the null-apiUser transient,
and confirm requiresAi: false programs (doctor) have no gate injected.

README adds a Privacy & data usage section and documents --no-telemetry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mount the real screen against a synthetic WizardStore pre-populated
with apiUser + credentials so the admin (membership_level >= 8) and
non-admin variants render without needing real PostHog accounts with
the toggle flipped off. Two tabs share one demo function.

Run with: pnpm try --playground, then arrow-key over to
"AI opt-in (admin)" / "AI opt-in (non-admin)".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previous URL was {cloud}/settings/organization-ai-consent which 404s.
Real path is {cloud}/project/{id}/settings/organization-details with
#organization-ai-consent as a fragment that scrolls to the toggle.

Pulls projectId off session.credentials. Falls back to a project-less
URL if it's somehow missing post-auth (defensive only — PostHog routes
to the user's default project in that case).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues observed in the playground demo:

1. Pressing [S] expanded a 4-row "Prefer your own AI? / Skill: / URL:"
   block that pushed the [O] action off the visible viewport in admin
   mode. Collapse to one inline line matching the PrivacyPanel pattern
   ("Prefer your own AI? Download the skill: <url>"). Saves ~3 rows.

2. The settings URL ternary used `projectId ? ... : ...` which treats
   0 as falsy and routed playground demo sessions (projectId = 0) to
   the project-less fallback. Switch to `projectId != null` so 0 still
   builds the project-scoped URL.

Drops the now-unused SkillSourceInfo import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…edScreen

The playground's TabContainer eats ~5 rows of chrome (tab bar + status
bar + demo header), which makes it impossible to see what the screen
actually looks like in production at default Terminal sizes.

scripts/preview-ai-opt-in.tsx mounts the real AiOptInRequiredScreen
against a synthetic store at full terminal height with no wrapper.

Usage:
  pnpm preview:ai-opt-in admin       # admin variant
  pnpm preview:ai-opt-in non-admin   # non-admin variant

Keys on the screen are LIVE — same caveat as the playground demo
(Ctrl-C to exit, [E] also exits, [O] opens a real browser tab).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the settings URL was built from getCloudUrlFromRegion(),
which hardcodes either us.posthog.com or eu.posthog.com based on the
local session.region. If session.region is wrong, stale, or somehow
missing, the user lands on the wrong region's domain.

Switch to https://app.posthog.com/... which PostHog redirects to the
user's actual region server-side based on their signed-in profile.
This is the standard convention for share-with-user links.

Adds POSTHOG_APP_URL constant alongside DEFAULT_URL. Keeps
getCloudUrlFromRegion in use for the retry's fetchUserData call, which
still needs to hit the user's region-specific API server.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
scripts/preview-ai-opt-in.tsx and the pnpm preview:ai-opt-in script
were a local-testing convenience. The AiOptInDemo in the playground
covers the same need for anyone else who needs to preview the screen.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI runs already auto-consent to AI usage per the README's "When running
in CI mode" notes, and the gate's interactive [O]/[R]/[E] flow would
be unworkable in a headless context anyway. Skip the gate when
session.ci is true.

show: returns false in CI so the router walks past the step.
isComplete: returns true in CI so the predicate is consistent (defensive
— show=false already skips it).

New test case covers the CI bypass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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