Skip to content

feat(messaging): give recipient preference writes their own scope - #128116

Merged
trunk-io[bot] merged 4 commits into
masterfrom
feat/messaging-preference-scope
Nov 3, 2026
Merged

trunk-io[bot] merged 4 commits into
masterfrom
feat/messaging-preference-scope

Conversation

@Silthus

@Silthus Silthus commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Impact

  • Goal: Grow Workflows. Signal: the new authorized_by property on messaging preferences api set, read weekly per scope after deploy.

Merge Danger

Caution

🔴 Merge risk: high · one-way door (merge-risk rubric)

  • Auth change on one public API route. Existing hog_flow:write keys keep working: the shared scope check accepts them on every credential type, and the fallback entry is permanent. Tests, a live run and a cross-version run show it.
  • Worst case if that check had a bug: existing keys get 403 on preference writes (visible), or a preference key can send. Tests and the live run show a preference key gets 403 on send and status.
  • One-way, and the reason for the grade: keys created with the new scope keep it after a revert.
  • Revert: git revert the merge commit. Keys holding only messaging_preference:write then get 403 on the route; nothing cleans them up, so fix forward instead.

Changes

  • New messaging_preference scope, write only, allowed on project secret API keys. The project secret key picker shows it as "Recipient preference", with a tooltip saying it can't send email.
  • The personal key picker and OAuth don't offer it, because the route accepts only project secret keys.
  • A "Set recipient preferences" key preset selects it.
  • POST messaging/preferences/ accepts messaging_preference:write or hog_flow:write. The send and status routes still need hog_flow.
  • hog_flow:write still works through the existing LEGACY_SCOPE_FALLBACKS, which records each use. The entry is marked permanent.
  • authorized_by comes from a new legacy_scope_object_used helper next to the scope permission, so the send scope in a later PR reuses it.
  • The event records authorized_by: messaging_preference or hog_flow.
  • The project secret key picker now shows a row's tooltip, and can name a scope that has no row in the other pickers.
  • Mechanical: OpenAPI text, regenerated types and scope projections.
Preset picks the scope Row says it can't send Personal key picker doesn't offer it
preset-picks-scope row-cannot-send personal-picker-hides

How did you test this code?

Test rationale: test_topic_preferences.py now runs on a messaging_preference:write key and covers both scopes plus authorized_by. It catches a fallback that drops hog_flow or a wrong attribution. The send and status scope tests gain a preference-key case, which catches a fallback wired the wrong way.

  • Red on the base code, green on HEAD: before the change, 16 cases fail (the preference key gets 403, and hog_flow keys miss authorized_by). After it, the same 38 selected cases pass, none skipped.
  • Shadow diff, base against HEAD over the same request matrix: every difference is a preference-key write that now succeeds or a new authorized_by value. hog_flow:write keys, send and status routes behave as before.
  • Cross-version: a key created after the deploy gets 403 from a pod still on the old code, until the rollout finishes. No key holds the new scope before the deploy. Keys with hog_flow:write keep working on both versions.
  • CI: every required check passed on the head commit. The optional Hogbox preview was cancelled while starting its frontend on every head and on one rerun. This PR does not touch the preview. Recorded as an accepted gap.
  • Prod shape: not measured. No stored key can hold the new scope before the deploy, so every existing key on this route takes the hog_flow fallback.
  • Live run on a local server at the head commit: the preference key sets preferences (200) and gets 403 on send, template send, status, and another project. A hog_flow:write key still sets them. Key creation refuses messaging_preference:read.
  • UI walk: five stages, from opening the settings page to the created key, then the personal key picker finding no match for the scope.
  • OAuth metadata on a local server no longer lists messaging_preference; hog_flow:write is still listed.
  • CI report: the TypeScript duplication warning pairs frontend/src/lib/oauthScopes.generated.ts with services/mcp/src/lib/oauth-scopes.generated.ts. hogli build:projections writes both, identical by design, so there is nothing to extract. The bundle size rows name files this PR does not change. The eager graph stays within budget. Comment density is 5%: the comments say why hog_flow stays a permanent fallback and why the key picker has its own rows. The one flaky Playwright test creates an experiment, which this PR does not touch, and it passed on retry.
  • Lane gate: ruff, oxlint, oxfmt, ci:preflight --strict, the scope, permission and messaging pytest suites, scopes.test.ts, ProjectSecretAPIKeys.test.tsx, and MCP tool-filtering.test.ts. frontend typecheck (tsgo, no errors).

Release status

  • No feature flag controls this change

Docs update

  • None here. The posthog.com page that names the scope follows with the SDK update.

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Question Answer Who Where
Old scope on the route hog_flow:write keeps working, no end date 👤 Human map
Scope name messaging_preference, write only 👤 Human map
Picker placement Only the project secret key picker; replaces the Messaging group 👤 Human ticket
Measure authorized_by on the event 👤 Human map
Either-scope check Existing LEGACY_SCOPE_FALLBACKS, not a new check 🤖 Agent comment
Preset Add "Set recipient preferences" 🤖 Agent comment
Shared fallback lookup Helper reads the token's scopes, not state set by the permission class 🤖 Agent comment
Project secret key modal Stays flat, shows row tooltips; grouping is a follow-up 🤖 Agent comment

Involvement: map grilled live (three rounds), design pass (GPT-6.1 Sol), one review round of five panel lanes plus three final reads, live run, UI walk, shadow diff and cross-version captured.

Agent: Claude Code, Claude Opus 5.5. Review: Codex, GPT-6.1 Sol.

Skills: /full-stop, /qa-swarm, /adding-api-scopes, /adding-project-secret-api-key-auth, /writing-tests, /writing-user-facing-copy, /writing-code-comments, /merge-risk, /pr-description, /writing-pr-descriptions

More context for AI agents
  • Session: T3 Code stack thread for PostHog/michaelr-context#2; Playwright UI walk, curl live run.
  • Decisions: see the Decisions table; the design pass asked to group the project secret key modal, rejected as out of scope.
  • Follow-ups: group the project secret key modal like the personal key picker.
  • Read closely: posthog/scopes.py (LEGACY_SCOPE_FALLBACKS), topic_preferences.py (scope_object, _authorized_by).

🤖 Generated with Claude Code

A project secret API key with messaging_preference:write can set a
recipient's topic preferences and cannot send email. hog_flow:write keeps
working on the route through LEGACY_SCOPE_FALLBACKS. The preferences
event records which scope authorized the call.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Silthus Silthus added hogbox-preview Spin up a per-PR PostHog preview on a hogland hogbox posthog-review labels Nov 3, 2026
@trunk-io

trunk-io Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

😎 This pull request was merged.

@Silthus Silthus self-assigned this Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

🤖 CI report

⚠️ Trunk lane — backend Python lane

This PR is assigned to the backend Python lane. It runs backend Python tests and may merge in parallel with PRs in other lanes.

✅ Complexity (TypeScript) — clean

Cyclomatic complexity above the limit in changed typescript files (10 for production files, 15 for test files). Warn only: worth simplifying when you next touch these functions.

✅ Duplication (Python) — clean

New Python code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

⚠️ Duplication (TypeScript) — 1 new duplicated block (worst 536 tokens)

New TypeScript code duplication introduced by this branch. Fails at 70+ tokens in app code, or 150+ tokens when both copies live in test files. Advisory while the gate proves itself: extract a shared helper instead of copying.

First copy Second copy Lines Tokens
frontend/src/lib/oauthScopes.generated.ts:12 services/mcp/src/lib/oauth-scopes.generated.ts:12 260 536
⚠️ Comment density — 5% of added code lines are comments (5 of 99)

This section warns when comments are more than 3% of the code lines a PR adds, and alerts above 6%. Before agent-assisted PRs, the typical share was about 2%. Only full-line comments count. Docstrings, generated files, snapshots, migrations, and workflow files are left out.

Comments that restate the code, record how the change came about, or narrate the next line add noise for the next reader. Keep the comments that explain a reason the code cannot show, and remove the rest. See .agents/skills/writing-code-comments/SKILL.md for the house rules.

Files with the most added comment lines:

File Comment lines Added lines
frontend/src/scenes/settings/project/projectSecretAPIKeysLogic.tsx 2 27
posthog/scopes.py 2 9
products/messaging/backend/presentation/views/topic_preferences.py 1 9

This check does not block merging. It updates on every push and clears when the share drops.

⚠️ Bundle size — 🔺 +21.7 KiB (+0.0%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 76.72 MiB · 🔺 +21.7 KiB (+0.0%)

File Size Δ vs base
exporter/src/exporter/scenes/ExporterNotebookScene.js 4.03 MiB 🔺 +106.2 KiB (+2.6%)
posthog-app/_parent/products/ai_observability/frontend/AIObservabilityTraceScene.js 141.3 KiB 🟢 -88.3 KiB (-38.5%)
posthog-app/_parent/products/data_modeling/frontend/ModelsScene.js 91.7 KiB 🔺 +2.1 KiB (+2.4%)

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

✅ Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.01 MiB · 25 files 🔺 +745 B (+0.1%) █████████░ 88.5% of 1.14 MiB
logged-out boot: index + App + bootApp (preloaded by every page, including /login)
src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
2.99 MiB · 622 files 🔺 +809 B (+0.0%) ██████████ 98.6% of 3.03 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
3.86 MiB · 1,238 files 🔺 +809 B (+0.0%) ██████████ 96.4% of 4.01 MiB
dashboard scene
src/scenes/dashboard/Dashboard.tsx
8.32 MiB · 3,061 files 🔺 +1022 B (+0.0%) ████████░░ 84.6% of 9.84 MiB
today home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
4.71 MiB · 1,761 files 🔺 +809 B (+0.0%) ██████████ 95.5% of 4.93 MiB
AI-first home path
src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
5.95 MiB · 2,258 files 🔺 +809 B (+0.0%) █████████░ 94.9% of 6.27 MiB
events scene
src/scenes/activity/explore/EventsScene.tsx
7.34 MiB · 2,624 files 🔺 +1022 B (+0.0%) ████████░░ 78.0% of 9.41 MiB
web analytics scene
src/scenes/web-analytics/WebAnalyticsScene.tsx
8.25 MiB · 2,949 files 🔺 +1022 B (+0.0%) ████████░░ 78.7% of 10.49 MiB
replay detail scene
src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
7.40 MiB · 2,653 files 🔺 +1.2 KiB (+0.0%) █████████░ 91.1% of 8.13 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 products/product_analytics/frontend/insights/activityDescriptions stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/index.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/layout/navigation-3000/navigationLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/scenes/dashboard/dashboardLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/lemon-ui/LemonMarkdown/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/RichContentEditor/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/CodeSnippet/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/zod/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/logic/billing/billingLogic.tsx stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 src/lib/components/ProductEmptyState/productSetupPreloadLogic.ts stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/kea-test-utils/ stays out of src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/product_analytics/frontend/insights/activityDescriptions stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/dashboards/frontend/widgets/previews/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/session-recordings/player/sessionRecordingPlayerLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/panel-layout/ProjectTree/menus/SessionReplayMenuItems.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/taxonomy/core-filter-definitions-by-group.json stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/navigation/projectNoticeLogic.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/navigation/ImpersonationNotice/ImpersonationNotice.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/cohorts/cohortSavedToast.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/wizard/frontend/runs/WizardRunDetailsDrawer.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodayShell.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodayContextPanelFloating.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodaySidePanel.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/TodayTopBar.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/layout/today/todayRailItems.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/subscriptions/frontend/components/Subscriptions/exportNudge/exportNudgeLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/billing/paymentEntryLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 products/conversations/frontend/components/SidePanel/sidepanelTicketsLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/zod/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/authentication/auth/loginLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/authentication/shared/passkeyLogic.ts stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/scenes/surveys/constants.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@tiptap/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/prosemirror- stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/re2js/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/react-markdown/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/Command/Command.tsx stays out of src/scenes/AuthenticatedShell.tsx
🟢 packages/quill/packages/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 zod/v4/locales/de.js stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/svg/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 node_modules/@posthog/brand/dist/generated/hoggies/components/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/queries/validators.js stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/queries/web-analytics-validators.js stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/re2js/ stays out of src/scenes/dashboard/Dashboard.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/scenes/debug/HogDebug.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 src/queries/QueryEditor/QueryEditor.tsx stays out of src/scenes/dashboard/Dashboard.tsx
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/scenes/project-homepage/today/TodayReportPage.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 src/queries/Query/Query.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 packages/quill/packages/charts/ stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 products/posthog_ai/frontend/components/tool/widgets/VisualizationWidget.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 products/posthog_ai/frontend/components/tool/widgets/RecordingsWidget.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/queries/Query/Query.tsx stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 products/posthog_ai/frontend/api/primitives.ts stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 src/scenes/notebooks/Notebook/notebookLogic.ts stays out of src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/session-recordings/playlist/SessionRecordingsPlaylist.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/web-analytics/tiles/WebAnalyticsTile.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/validators.js stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/web-analytics-validators.js stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/re2js/ stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/nodes/InsightViz/InsightViz.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/nodes/DataVisualization/DataVisualization.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/nodes/WebOverview/WebOverview.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/scenes/debug/HogDebug.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 src/queries/QueryEditor/QueryEditor.tsx stays out of src/scenes/activity/explore/EventsScene.tsx
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 src/scenes/web-analytics/LiveMetricsDashboard/LiveWebAnalyticsMetrics.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 products/product_analytics/frontend/insights/trends/WorldMap/countryVectors.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 src/queries/validators.js stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/re2js/ stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 src/scenes/debug/HogDebug.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 src/queries/QueryEditor/QueryEditor.tsx stays out of src/scenes/web-analytics/WebAnalyticsScene.tsx
🟢 packages/quill/packages/quill/dist/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 src/scenes/settings/SettingsMap.tsx stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 @jspm/core/nodelibs/browser/fs stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/re2js/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/kea-test-utils/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
🟢 node_modules/d3/src/ stays out of src/scenes/session-recordings/detail/SessionRecordingDetail.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.7 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
4.0 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.9 KiB src/lib/utils/bootTiming.ts
1.7 KiB src/index.tsx
1.7 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.2 KiB src/RootErrorBoundary.tsx
1.1 KiB src/lib/utils/deferredAppContext.ts
Largest files eagerly shipped from src/index.tsx + src/scenes/App.tsx + src/scenes/bootApp.ts
Size File
355.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
104.2 KiB src/products.tsx
88.3 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
41.9 KiB ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js
36.4 KiB ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js
28.3 KiB src/lib/constants.tsx
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
355.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
104.2 KiB src/products.tsx
88.3 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
41.9 KiB ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js
36.4 KiB ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js
32.7 KiB src/lib/utils/eventUsageLogic.ts
28.3 KiB src/lib/constants.tsx
Largest files eagerly shipped from src/scenes/dashboard/Dashboard.tsx
Size File
355.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
114.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/surveys-preview.js
108.9 KiB ../packages/quill/packages/primitives/dist/index.js
104.2 KiB src/products.tsx
94.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.6 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.4 KiB ../packages/quill/packages/components/dist/index.js
88.3 KiB src/lib/api.ts
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/today/TodayHome.tsx
Size File
355.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
108.9 KiB ../packages/quill/packages/primitives/dist/index.js
104.2 KiB src/products.tsx
88.3 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
68.0 KiB ../products/posthog_ai/frontend/logics/runStreamLogic.ts
41.9 KiB ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js
36.4 KiB ../node_modules/.pnpm/kea@4.0.0-pre.6_patch_hash=139b8d1f1304f9d9da452a9a1244c94ea679dbcb85687d8999563146879fb6f5_react@18.3.1/node_modules/kea/lib/index.cjs.js
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx + src/scenes/project-homepage/ProjectHomepage.tsx + src/scenes/project-homepage/ai-first/AiFirstHomepage.tsx
Size File
355.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
108.9 KiB ../packages/quill/packages/primitives/dist/index.js
104.2 KiB src/products.tsx
88.3 KiB src/lib/api.ts
68.7 KiB src/lib/lemon-ui/icons/icons.tsx
68.0 KiB ../products/posthog_ai/frontend/logics/runStreamLogic.ts
41.9 KiB ../node_modules/.pnpm/@dnd-kit+core@6.0.8_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@dnd-kit/core/dist/core.esm.js
41.7 KiB src/scenes/hog-functions/sub-templates/sub-templates.ts
Largest files eagerly shipped from src/scenes/activity/explore/EventsScene.tsx
Size File
355.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
108.9 KiB ../packages/quill/packages/primitives/dist/index.js
104.2 KiB src/products.tsx
94.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.6 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.4 KiB ../packages/quill/packages/components/dist/index.js
88.3 KiB src/lib/api.ts
75.3 KiB src/scenes/dashboard/dashboardLogic.tsx
Largest files eagerly shipped from src/scenes/web-analytics/WebAnalyticsScene.tsx
Size File
355.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
224.1 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
114.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/surveys-preview.js
108.9 KiB ../packages/quill/packages/primitives/dist/index.js
104.2 KiB src/products.tsx
94.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.6 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.4 KiB ../packages/quill/packages/components/dist/index.js
88.3 KiB src/lib/api.ts
Largest files eagerly shipped from src/scenes/session-recordings/detail/SessionRecordingDetail.tsx
Size File
355.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/module.mjs
321.5 KiB ../node_modules/.pnpm/posthog-js@1.444.4_@types+react@18.3.27_react@18.3.1/node_modules/posthog-js/dist/rrweb.js
224.1 KiB ../node_modules/.pnpm/@posthog+icons@0.39.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
108.9 KiB ../packages/quill/packages/primitives/dist/index.js
104.2 KiB src/products.tsx
94.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js
92.6 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.4 KiB ../packages/quill/packages/components/dist/index.js
88.3 KiB src/lib/api.ts

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

✅ Frontend layers — no new upward imports, 0 removed

Static imports from a lower frontend layer into a higher one (layers, lowest first: kernel, shell, query, features). Compared with the base branch, so an import that a PR also adds to layer-baseline.txt still shows here. Sizes are the source bytes on the eager path of every logged-in page.

Layer Eager source bytes Δ vs base
kernel 2.97 MiB 🔺 +291 B (+0.0%)
shell 2.39 MiB 🔺 +524 B (+0.0%)
query 56.4 KiB no change
features 378.4 KiB no change

Download the interactive layer graph for this PR.

Posted automatically by check-layers

✅ Toolbar bundle — eager 2.37 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.37 MiB · 19 files 🔺 +693 B (+0.0%) ████░░░░░░ 41.4% of 5.72 MiB
Deferred (lazy) 2.11 MiB · 42 files no change n/a — loads on demand
Loader dist/toolbar.js 1.2 KiB no change █░░░░░░░░░ 6.0% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
947.8 KiB dist/toolbar/toolbar-app-IGL4DHA7.css
726.8 KiB dist/toolbar/chunk-chunk-JTS2WDVZ.js
261.2 KiB dist/toolbar/chunk-chunk-FTMGICRW.js
131.8 KiB dist/toolbar/chunk-chunk-FDH2IBXT.js
130.3 KiB dist/toolbar/chunk-chunk-Z7V352IS.js
75.7 KiB dist/toolbar/toolbar-app-BRXABF4P.js
69.0 KiB dist/toolbar/chunk-chunk-TSAL54PB.js
33.8 KiB dist/toolbar/chunk-chunk-C2JAWSHT.js
21.7 KiB dist/toolbar/chunk-chunk-GBEZW6F6.js
6.8 KiB dist/toolbar/chunk-chunk-DV7IWQNF.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

✅ Dist folder size — 🔺 +1015.9 KiB (+0.1%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 970.16 MiB · 🔺 +1015.9 KiB (+0.1%)

ℹ️ MCP UI apps size — 35 app(s), 29126.7 KB JS

Built size of each MCP UI app (main.js + styles.css).

App JS CSS
debug 844.4 KB 211.6 KB
explain-chart 1119.2 KB 211.6 KB
action 767.8 KB 211.6 KB
action-list 835.4 KB 211.6 KB
cohort 766.8 KB 211.6 KB
cohort-list 834.4 KB 211.6 KB
email-template 766.6 KB 211.6 KB
error-details 771.7 KB 211.6 KB
error-issue 769.0 KB 211.6 KB
error-issue-list 836.8 KB 211.6 KB
experiment 832.5 KB 211.6 KB
experiment-list 836.1 KB 211.6 KB
experiment-results 834.3 KB 211.6 KB
feature-flag 834.8 KB 211.6 KB
feature-flag-list 838.5 KB 211.6 KB
feature-flag-testing 770.0 KB 211.6 KB
inline-scan 767.2 KB 211.6 KB
insight-actors 833.8 KB 211.6 KB
llm-costs 831.1 KB 211.6 KB
session-recording 769.0 KB 211.6 KB
survey 768.4 KB 211.6 KB
survey-global-stats 831.2 KB 211.6 KB
survey-list 836.1 KB 211.6 KB
survey-stats 831.2 KB 211.6 KB
trace-span 767.1 KB 211.6 KB
trace-span-list 835.3 KB 211.6 KB
vision-observation-list 834.5 KB 211.6 KB
workflow 767.1 KB 211.6 KB
workflow-list 834.7 KB 211.6 KB
workflow-setup-status 766.8 KB 211.6 KB
query-results 1090.8 KB 211.6 KB
render-ui 1195.1 KB 211.6 KB
visual-review-snapshots 771.6 KB 211.6 KB
workflow-data-suggestions 769.4 KB 211.6 KB
workflow-sending-rules 768.1 KB 211.6 KB
⚠️ Playwright — 1 flaky

🎭 Playwright report · View test results →

⚠️ 1 flaky test:

  • create experiment via wizard, add metrics, and launch (chromium)

These issues are not necessarily caused by your changes.
Annoyed by this section? Help fix flakies and failures and it will go green!

ℹ️ Hogbox preview — building

Spinning up PostHog for this PR on a hogland hogbox. This section updates in place when it's ready (usually a few minutes).

commit 36431c2 · build log

@Silthus

Silthus commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor Author

Note

🤖 Claude Opus 5.5 responding on behalf of Michael

Decisions made while building this PR:

  • 🤖 Where the either-scope check lives The existing LEGACY_SCOPE_FALLBACKS map, not a new check in the view. It already lets a legacy scope satisfy a split-off one, records each use, and reaches the MCP server. The send scope reuses it with one more entry.
  • 🤖 Preset Added "Set recipient preferences" to the project secret key presets, so a consent-only key takes one click.
  • 🤖 Project secret key modal Kept its flat list and passed each row's tooltip through, so the row says it can't send email. Grouping that modal like the personal key picker is a follow-up.
  • 🤖 One-object group The Messaging group holds one object until the send scope joins it, per the decided group.
  • 🤖 Shared fallback lookup legacy_scope_object_used in posthog/permissions.py works out authorized_by from the token's scopes and the shared map. The alternative was to store the permission class's result on the request. Not taken, because the view would then depend on the permission class having run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Silthus
Silthus marked this pull request as ready for review November 3, 2026 16:05
@parameterai

parameterai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Risk: No findings

This PR splits recipient preference writes onto a new messaging_preference:write scope, allowed only on project secret API keys, with hog_flow:write kept as a permanent legacy fallback and a new authorized_by analytics property. The privilege boundary is preserved in the intended direction: a preference-scoped key cannot reach the send/status routes (the fallback maps new→legacy only), and a hog_flow-scoped credential cannot mint a preference-scoped key (issuance skips the fallback). No new vulnerabilities found.

Sentinel reviewed 36431c2 · Review settings

@pr-assigner-resolver-posthog
pr-assigner-resolver-posthog Bot requested a review from a team November 3, 2026 16:06
@trunk-io

trunk-io Bot commented Nov 3, 2026

Copy link
Copy Markdown

This pull request is queued for merge as part of 128166, which will merge 128116, 128166.

@trunk-io
trunk-io Bot merged commit 22c17b5 into master Nov 3, 2026
572 of 584 checks passed
@trunk-io
trunk-io Bot deleted the feat/messaging-preference-scope branch November 3, 2026 20:42
@trunk-io

trunk-io Bot commented Nov 3, 2026

Copy link
Copy Markdown

This pull request was merged into master as part of stacked PR 128166.

@deployment-status-posthog

deployment-status-posthog Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-11-03 21:27 UTC Run
prod-us ✅ Deployed 2026-11-03 21:47 UTC Run
prod-eu ✅ Deployed 2026-11-03 21:49 UTC Run

This branch is being deployed

1 in progress deployment
hogbox-preview — 36431c20 Deployed Nov 3, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

hogbox-preview Spin up a per-PR PostHog preview on a hogland hogbox

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants