Files
3a16bc4889 feat(dashboards): add a metrics table widget with one row per label value (#122790)
Co-authored-by: tests-posthog[bot] <250237707+tests-posthog[bot]@users.noreply.github.com>
Co-authored-by: posthog[bot] <206114724+posthog[bot]@users.noreply.github.com>
Co-authored-by: DanielVisca <16750763+DanielVisca@users.noreply.github.com>
2026-10-23 21:56:31 +00:00

1861 lines
48 KiB
TOML

layers = [
"modules",
]
exclude = [
"**/*__pycache__",
"**/*egg-info",
"docs",
# Test directories stay in the graph so that a test outside a product that imports the
# product's internals is an interface violation. They are not dependency edges: `hogli
# lint:tach` (what CI runs) excludes test code from the dependency pass only.
# Eval trees are dev/CI-only tooling: the harness orchestrates many products by
# design, and product-owned suites under products/*/evals import the harness
# contracts, so neither fits the production dependency graph. Signals keeps its suite
# under eval/ rather than evals/, so it is listed by path.
"products/posthog_ai/eval_harness",
"products/*/evals",
"products/signals/eval",
]
source_roots = [
".",
]
[[modules]]
path = "<root>"
depends_on = [
]
layer = "modules"
[[modules]]
path = "common.hogql_parser"
depends_on = []
layer = "modules"
utility = true
[[modules]]
path = "common.hogvm.python"
depends_on = [
"posthog",
]
layer = "modules"
utility = true
[[modules]]
path = "ee"
depends_on = [
# Facade-only: a Slack subscription asks whether the bot can answer a follow-up on its report.
"products.slack_app",
"products.cohorts",
"posthog",
"products.access_control",
"products.actions",
"products.approvals",
"products.alerts",
"products.cdp",
"products.conversations",
"products.customer_analytics",
"products.dashboards",
"products.data_modeling",
"products.data_tools",
"products.demo",
"products.error_tracking",
"products.event_definitions",
"products.experiments",
"products.feature_flags",
"products.ai_observability",
"products.mcp_store",
"products.notebooks",
"products.notifications",
"products.posthog_ai",
"products.product_analytics",
"products.replay",
"products.replay_vision",
"products.skills",
"products.revenue_analytics",
"products.security",
"products.subscriptions",
"products.surveys",
"products.tasks",
"products.business_knowledge",
"products.warehouse_sources",
"products.exports",
"products.workflows",
]
layer = "modules"
[[modules]]
path = "posthog"
depends_on = [
"products.account_audits",
"products.alerts_platform",
"products.ai_training",
"products.autoresearch",
"products.cohorts",
"ee",
"products.access_control",
"products.aeo",
"products.analytics_platform",
"products.approvals",
"products.batch_exports",
"products.billing_alerts",
"products.business_knowledge",
"products.canvas",
"products.cdp",
"products.context_layer",
"products.conversations",
"products.customer_analytics",
"products.dashboards",
"products.data_catalog",
"products.data_modeling",
"products.data_quality",
"products.data_warehouse",
"products.demo",
"products.early_access_features",
"products.endpoints",
"products.engineering_analytics",
"products.error_tracking",
"products.event_definitions",
"products.experiments",
"products.feature_flags",
"products.growth",
"products.legal_documents",
"products.links",
"products.ai_observability",
"products.logs",
"products.managed_warehouse",
"products.marketing_analytics",
"products.messaging",
"products.metrics",
"products.notebooks",
"products.notifications",
"products.posthog_ai",
"products.product_analytics",
"products.product_tours",
"products.pulse",
"products.reminders",
"products.replay",
"products.replay_vision",
"products.revenue_analytics",
"products.review_hog",
"products.signals",
"products.skills",
"products.slack_app",
"products.stamphog",
"products.streamlit_apps",
"products.subscriptions",
"products.surveys",
"products.tasks",
"products.today",
"products.tracing",
"products.visual_review",
"products.warehouse_suggestions",
"products.mcp_analytics",
"products.mcp_registry",
"products.web_analytics",
"products.wizard",
"products.workflows",
"products.actions",
"products.alerts", "products.warehouse_sources", "products.data_tools", "products.exports", "products.annotations",
"products.security",
"products.webmcp",
]
layer = "modules"
[[modules]]
path = "products.actions"
depends_on = [
"products.cohorts",
"posthog",
"products.cdp",
"products.experiments",
"products.feature_flags",
"products.product_analytics", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.aeo"
depends_on = ["posthog"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
]
from = [
"products.aeo",
]
[[modules]]
path = "products.alerts"
depends_on = [
"products.alerts_platform",
"ee",
"posthog",
"products.cdp",
"products.product_analytics", "products.access_control",
]
layer = "modules"
# alerts — external consumers go through the facade: facade.contracts for the
# destination and delivery types, facade.destinations for destination configuration and
# dispatch, facade.email for alert mail, facade.lifecycle for the shared state machine,
# facade.scheduling for the scheduling math, facade.delivery_slo for delivery SLOs,
# facade.evaluation for the threshold comparator, and facade.api for the insight-alert reads
# and writes. presentation.views carries the DRF
# surface adopter products mount or reuse in process, which a facade may not hold.
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
"backend\\.routes.*",
]
from = [
"products.alerts",
]
[[modules]]
path = "products.analytics_platform"
depends_on = [
"posthog",
]
layer = "modules"
[[modules]]
path = "products.approvals"
depends_on = [
"ee",
"posthog",
"products.feature_flags",
"products.notifications", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.autoresearch"
depends_on = [
"posthog",
"products.actions",
"products.feature_flags",
"products.notebooks",
"products.tasks",
]
layer = "modules"
[[modules]]
path = "products.batch_exports"
depends_on = [
"ee",
"posthog",
"products.access_control",
"products.notifications",
# Batch export runs emit internal events through products.cdp.backend.internal_events.
"products.cdp",
]
layer = "modules"
[[modules]]
path = "products.cdp"
depends_on = [
"products.cohorts",
"ee",
"posthog",
"products.actions",
"products.batch_exports",
]
layer = "modules"
[[modules]]
path = "products.cohorts"
depends_on = [
"products.actions",
"posthog",
# A behavioural cohort filter reads a trends insight's display type.
"products.product_analytics",
]
layer = "modules"
[[modules]]
path = "products.conversations"
depends_on = [
"ee",
"posthog", "products.tasks", "products.business_knowledge", "products.warehouse_sources", "products.customer_analytics", "products.access_control",
# Facade-only: the deleted-ticket purge retracts the ticket's signal embeddings. products.signals
# depends on products.conversations in turn, so keep this direction on the facade.
"products.signals",
]
layer = "modules"
[[modules]]
path = "products.core_events"
depends_on = []
layer = "modules"
[[modules]]
path = "products.dashboards"
depends_on = [
"products.notebooks",
"ee",
"posthog",
"products.alerts",
"products.ai_observability",
"products.error_tracking",
"products.experiments",
"products.feature_flags",
"products.logs",
"products.mcp_analytics",
# Facade-only: the metrics_table widget runs its query through the metrics facade.
"products.metrics",
"products.notifications",
"products.product_analytics",
"products.conversations",
"products.surveys", "products.access_control",
# The canvas_app widget resolves its canvas through the canvas facade as the viewer.
"products.canvas",
# Facade-only: the dashboard filter access check asks the subscriptions facade whether a
# subscription delivers the dashboard.
"products.subscriptions",
]
layer = "modules"
[[modules]]
path = "products.data_modeling"
depends_on = [
"posthog",
"products.data_tools",
"products.data_warehouse",
"products.revenue_analytics",
"products.warehouse_sources", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.data_tools"
depends_on = ["posthog"]
layer = "modules"
[[modules]]
path = "products.games"
depends_on = []
layer = "modules"
[[modules]]
path = "products.groups"
depends_on = [
"posthog",
"products.access_control",
"products.event_definitions",
"products.notebooks",
"products.warehouse_sources",
]
layer = "modules"
[[modules]]
path = "products.ingestion"
depends_on = []
layer = "modules"
[[modules]]
path = "products.integrations"
depends_on = []
layer = "modules"
[[modules]]
path = "products.persons"
depends_on = []
layer = "modules"
[[modules]]
path = "products.session_replay"
depends_on = []
layer = "modules"
[[modules]]
path = "products.session_summaries"
depends_on = []
layer = "modules"
[[modules]]
path = "products.subscriptions"
depends_on = [
"posthog",
"ee",
"products.access_control",
"products.ai_observability",
"products.annotations",
"products.dashboards",
"products.exports",
"products.notifications",
"products.posthog_ai",
"products.product_analytics",
# Facade-only: a Slack subscription asks whether the bot can answer a follow-up on its report.
"products.slack_app",
]
layer = "modules"
[[modules]]
path = "products.support"
depends_on = []
layer = "modules"
[[modules]]
path = "products.toolbar"
depends_on = []
layer = "modules"
[[modules]]
path = "products.warehouse_sources"
depends_on = [
"ee",
"posthog",
"products.batch_exports",
"products.cdp",
"products.data_modeling",
"products.data_tools",
"products.data_warehouse",
"products.notifications",
"products.posthog_ai",
"products.revenue_analytics",
"products.warehouse_sources_queue",
"products.workflows",
"products.managed_warehouse", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.data_warehouse"
depends_on = [
"ee",
"posthog",
"products.cdp",
"products.data_quality",
"products.data_modeling",
"products.revenue_analytics", "products.warehouse_sources", "products.data_tools", "products.batch_exports",
"products.warehouse_sources_queue",
"products.managed_warehouse", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.demo"
depends_on = [
"posthog",
"products.actions",
"products.cohorts",
"products.dashboards",
"products.data_tools",
"products.endpoints",
"products.error_tracking",
"products.event_definitions",
"products.experiments",
"products.feature_flags",
"products.product_analytics",
"products.warehouse_sources",
]
layer = "modules"
[[modules]]
path = "products.early_access_features"
depends_on = [
"posthog",
"ee",
"products.feature_flags", "products.access_control",
# Enrollment changes emit internal events through products.cdp.backend.internal_events.
"products.cdp",
]
layer = "modules"
[[modules]]
path = "products.endpoints"
depends_on = [
"posthog",
"products.data_modeling",
"products.data_warehouse",
"products.product_analytics",
"products.signals", "products.warehouse_sources",
"products.managed_warehouse", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.error_tracking"
depends_on = [
"products.cohorts",
"ee",
"posthog",
"products.cdp",
# Facade-only: new issue severity inference calls the shared decision-model client.
"products.ml_inference",
"products.notifications",
"products.signals",
"products.warehouse_sources", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.event_definitions"
depends_on = [
"posthog",
]
layer = "modules"
[[modules]]
path = "products.experiments"
depends_on = [
"products.cohorts",
"ee",
"posthog",
"products.access_control",
"products.actions",
"products.approvals",
"products.ai_observability",
"products.analytics_platform",
"products.data_tools",
"products.event_definitions",
"products.feature_flags",
"products.notifications",
# An experiment with a funnel metric runs the funnel through product analytics.
"products.product_analytics",
"products.product_tours",
"products.surveys",
"products.tasks",
"products.warehouse_sources",
]
layer = "modules"
[[modules]]
path = "products.feature_flags"
depends_on = [
"products.cohorts",
"ee",
"posthog",
"products.alerts",
"products.approvals",
"products.dashboards",
"products.early_access_features",
"products.experiments",
"products.subscriptions",
"products.product_analytics",
"products.product_tours",
"products.surveys", "products.access_control",
"products.notifications",
]
layer = "modules"
[[modules]]
path = "products.growth"
depends_on = [
"ee",
"posthog", "products.access_control",
"products.signals", "products.tasks",
"products.warehouse_sources", "products.wizard",
]
layer = "modules"
[[modules]]
# Retired product. The app keeps its model and migration history so that Django keeps
# cascading deletes into its table until a later migration drops it.
path = "products.links"
depends_on = ["posthog"]
layer = "modules"
[[modules]]
# Retired product. The app keeps its model and migration history so that Django keeps
# cascading deletes into its table until a later migration drops it.
path = "products.live_debugger"
depends_on = ["posthog"]
layer = "modules"
[[modules]]
path = "products.ai_observability"
depends_on = [
"products.cohorts",
"ee",
"posthog",
"products.dashboards",
"products.exports",
"products.workflows", "products.access_control",
# Evaluations and taggers compile Hog through products.cdp.backend.validation and filters.
"products.cdp",
]
layer = "modules"
[[modules]]
# Vestigial app — only retains the migration history under the llm_analytics
# Django app label. All models and code live under products.ai_observability now.
# Historical migration files reference posthog.models.utils etc.; allowed because
# they're frozen history, not new code.
path = "products.llm_analytics"
depends_on = ["posthog"]
layer = "modules"
[[modules]]
path = "products.logs"
depends_on = [
"products.alerts_platform",
"posthog",
"products.alerts",
"products.apm",
"products.cdp",
"products.exports",
"products.ml_inference",
"products.signals", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.billing_alerts"
depends_on = [
"products.alerts_platform",
"ee",
"posthog",
"products.alerts",
"products.cdp",
]
layer = "modules"
[[modules]]
path = "products.context_layer"
depends_on = [
"ee",
"posthog",
"products.tasks", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.managed_migrations"
depends_on = [
"posthog",
"products.batch_exports",
]
layer = "modules"
[[modules]]
path = "products.mcp_analytics"
depends_on = [
"posthog",
"products.dashboards", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.mcp_registry"
depends_on = [
"posthog",
# Facade-only (enforced by mcp_registry's [[interfaces]] block): the measured-server
# aggregation reads new-SDK $mcp_tool_call grouping rules through mcp_analytics' facade.
"products.mcp_analytics",
]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
"backend\\.routes.*",
]
from = [
"products.mcp_registry",
]
[[modules]]
path = "products.mcp_store"
depends_on = [
"ee",
"posthog", "products.access_control",
# Presentation-only: the server icon endpoint serves through
# products.cdp.backend.presentation.icons.
"products.cdp",
]
layer = "modules"
[[modules]]
path = "products.messaging"
depends_on = [
"posthog",
# Facade-only: template writes publish the realtime edited-elsewhere event through
# products.notifications.backend.facade.api.
"products.notifications",
# Facade-only: the recipient preferences page reads EmailTrackingConsentMode through
# products.workflows.backend.facade.enums.
"products.workflows",
# Presentation-only: the template design action wraps HTML through
# products.cdp.backend.presentation.hog_functions.
"products.cdp",
]
layer = "modules"
[[modules]]
path = "products.metrics"
depends_on = [
"ee",
"posthog",
"products.access_control",
"products.dashboards",
"products.error_tracking",
"products.exports",
"products.tasks",
]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.api.*",
]
from = [
"products.metrics",
]
[[modules]]
path = "products.notebooks"
depends_on = [
"ee",
"posthog",
"products.canvas",
"products.dashboards",
"products.product_analytics",
"products.tasks", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.pulse"
depends_on = [
"ee",
"posthog",
"products.ai_observability",
"products.alerts",
"products.annotations",
"products.dashboards",
"products.experiments",
"products.subscriptions",
"products.product_analytics", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.streamlit_apps"
depends_on = [
"posthog",
"products.tasks",
]
layer = "modules"
[[modules]]
path = "products.notifications"
depends_on = [
"ee",
"posthog", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.platform_features"
depends_on = [
"ee",
"posthog",
]
layer = "modules"
[[modules]]
path = "products.posthog_ai"
depends_on = [
"ee",
"posthog",
"products.error_tracking",
"products.event_definitions",
"products.ai_observability",
"products.logs",
# The skill build reads the bundled-skill names the skills store validates against, so the
# two sides share one definition. The skills product has no facade, so this matches how
# review_hog, signals and tasks reach it.
"products.skills",
"products.signals",
"products.tasks",
]
layer = "modules"
[[modules]]
path = "products.product_analytics"
depends_on = [
"products.cohorts",
"products.marketing_analytics",
"ee",
"posthog",
"products.actions",
"products.alerts",
"products.dashboards",
"products.event_definitions",
"products.subscriptions",
"products.annotations",
"products.warehouse_sources", "products.access_control",
# A trends query filtering on a first-pageview-aware property builds it through
# web analytics' attribution helper. Core held this import before trends moved.
"products.web_analytics",
]
layer = "modules"
# product_analytics — external consumers go through the facade: facade.api for data
# capabilities, facade.queries for the QueryRunner wiring core dispatches on, and
# facade.models for the watched-models allowance crossings (Insight, InsightVariable,
# InsightViewed — see MODEL_CROSSINGS and architecture.md § Wiring couplings).
# presentation.* carries the DRF serializers sharing, dashboards and alerts use to
# render insights in-process — facades must not import DRF, so presentation exposure
# is the sanctioned channel (products.metrics precedent).
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation.*",
"backend\\.routes.*",
]
from = [
"products.product_analytics",
]
[[modules]]
path = "products.product_tours"
depends_on = [
"posthog",
"products.approvals",
"products.feature_flags",
"products.surveys", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.reminders"
depends_on = [
"posthog",
"products.access_control",
"products.dashboards",
"products.error_tracking",
"products.experiments",
"products.feature_flags",
"products.notebooks",
"products.notifications",
"products.product_analytics",
"products.surveys",
]
layer = "modules"
[[modules]]
path = "products.replay"
depends_on = [
"ee",
"posthog", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.replay_vision"
depends_on = [
"products.alerts_platform",
"ee",
"posthog",
"products.actions",
"products.alerts",
"products.cdp",
"products.cohorts",
# Facade-only: scanner dashboards are created and looked up through the dashboards facade.
"products.dashboards",
"products.event_definitions",
"products.experiments",
"products.exports",
"products.feature_flags",
# Facade-only: the watch feed's hourly Jev sweep calls the shared decision-model client.
"products.ml_inference",
"products.notifications",
"products.signals",
"products.tasks", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.revenue_analytics"
depends_on = [
"ee",
"posthog", "products.warehouse_sources", "products.data_modeling", "products.data_tools", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.review_hog"
depends_on = [
"ee",
"posthog",
"products.engineering_analytics",
"products.signals",
"products.skills",
# Facade-only (enforced by stamphog's [[interfaces]] block): the inbox trigger queues
# hosted Stamphog reviews and registers the toggle resolver hook.
"products.stamphog",
"products.tasks",
]
layer = "modules"
[[modules]]
path = "products.signals"
depends_on = [
# Facade-only: a scout report asks whether the bot can answer a follow-up on it. products.slack_app
# depends on products.signals in turn, so both directions stay facade-only to keep the pair honest.
"products.slack_app",
"ee",
"products.cohorts",
"posthog",
"products.actions",
"products.alerts",
# Facade-only: anomaly scoring spreads insights over the window after each boundary like alert checks.
"products.alerts_platform",
"products.cdp",
"products.conversations",
"products.dashboards",
# Facade-only: reviewer suggestions read the GitHub team roster to route work at a team slug.
"products.engineering_analytics",
# Facade-only (enforced by data_catalog's [[interfaces]] block): the scout harness gates
# its catalog-first prompt rule on the data catalog flag.
"products.data_catalog",
"products.data_warehouse",
"products.error_tracking",
"products.experiments",
# Facade-only: scout report Slack delivery renders report charts to PNG through the
# exports facade and mints their delivery urls there.
"products.exports",
"products.feature_flags",
# Facade-only: the scout harness pre-resolves the external MCP servers a run's sandbox will
# mount so the run prompt can name them.
"products.mcp_store",
# Facade-only: actionability and safety experiments call the shared decision-model client.
"products.ml_inference",
# Facade-only: the quota gate tells the org its self-driving credits limit paused scout runs.
"products.notifications",
"products.notebooks",
"products.product_analytics",
# Facade-only: Replay Vision authorizes signal emission per scanner rather than through a
# SignalSourceConfig row, so onboarding has to ask it whether any scanner is emitting.
"products.replay_vision",
"products.skills",
"products.surveys",
"products.tasks",
"products.warehouse_sources",
"products.workflows", "products.business_knowledge", "products.access_control",
]
layer = "modules"
[[modules]]
# Skills imports ai_observability's shared markdown_outline + API metrics helpers —
# temporary coupling from the standalone-product extraction, slated for cleanup.
path = "products.skills"
depends_on = [
"ee",
"posthog",
# Facade-only: authenticated scout runs read their pinned skill variant.
"products.signals",
"products.ai_observability", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.slack_app"
depends_on = [
"posthog",
"products.alerts",
"products.canvas",
"products.cohorts",
"products.conversations",
"products.dashboards",
"products.error_tracking",
"products.experiments",
"products.feature_flags",
"products.notebooks",
"products.product_analytics",
"products.signals",
"products.surveys",
"products.tasks", "products.access_control",
# Slack triggers emit internal events through products.cdp.backend.internal_events.
"products.cdp",
# Mentions that name a pull request and a command run through the commands facade.
"products.commands",
# Link unfurls render insight charts through the exports facade.
"products.exports",
# Mention workflows check billing quota limits before they start a task.
"ee",
# Feature tips skip MCP tools and Slack subscriptions that the reader already uses.
"products.mcp_store",
"products.subscriptions",
]
layer = "modules"
[[modules]]
path = "products.surveys"
depends_on = [
"products.cohorts",
"ee",
"posthog",
"products.actions",
"products.approvals",
"products.feature_flags",
"products.product_analytics",
"products.product_tours", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.customer_analytics"
depends_on = [
"ee",
"posthog",
"products.actions",
"products.cdp",
"products.conversations",
"products.data_modeling",
"products.data_warehouse",
"products.notebooks",
"products.notifications",
"products.product_analytics",
"products.warehouse_sources",
"products.workflows", "products.access_control",
]
layer = "modules"
[[modules]]
path = "products.managed_warehouse"
depends_on = [
"ee",
"posthog",
"products.data_warehouse",
"products.data_modeling",
"products.warehouse_sources",
"products.access_control",
]
layer = "modules"
[[modules]]
path = "products.marketing_analytics"
depends_on = [
"ee",
"posthog",
"products.access_control",
"products.actions",
"products.event_definitions",
"products.analytics_platform", "products.warehouse_sources",
"products.feature_flags",
]
layer = "modules"
[[modules]]
path = "products.tasks"
depends_on = [
"products.cdp",
"ee",
"posthog",
"products.canvas",
"products.context_layer",
"products.error_tracking",
"products.event_definitions",
"products.exports",
"products.feature_flags",
"products.ai_observability",
"products.mcp_store",
"products.notifications",
"products.posthog_ai",
"products.signals",
# Skills attached to workflow task nodes are read at fire time. The skills
# product has no facade, so this matches how review_hog and signals reach LLMSkill.
"products.skills",
"products.slack_app", "products.access_control",
"products.security",
]
layer = "modules"
[[modules]]
path = "products.canvas"
depends_on = [
"products.workflows",
"posthog",
# Sandbox provisioning (via the tasks facade) and the Channel FK canvases file into.
"products.tasks",
# The annotations.create action verb (via the annotations facade).
"products.annotations",
# MCP connector calls made with the viewer's own installation (via the mcp_store facade).
"products.mcp_store",
# Object access checks on canvases served as contracts (via the access_control facade).
"products.access_control",
]
layer = "modules"
[[modules]]
path = "products.tracing"
depends_on = [
"posthog", "products.access_control",
"products.logs",
]
layer = "modules"
[[modules]]
# Retired product. The app keeps its models and migration history so that Django keeps
# cascading deletes into its tables until a later migration drops them.
path = "products.user_interviews"
depends_on = ["posthog"]
layer = "modules"
[[modules]]
path = "products.visual_review"
depends_on = [
"posthog",
]
layer = "modules"
[[modules]]
path = "products.warehouse_sources_queue"
depends_on = [
"posthog",
]
layer = "modules"
[[modules]]
path = "products.web_analytics"
depends_on = [
"products.access_control",
"products.aeo",
"products.cohorts",
"ee",
"posthog",
"products.actions",
"products.analytics_platform",
"products.notifications",
# The web trends, web vitals and calendar heatmap runners subclass product analytics' trends runners.
"products.product_analytics",
"products.replay_vision",
]
layer = "modules"
[[modules]]
path = "products.workflows"
depends_on = [
"products.cohorts",
"ee",
"posthog",
"products.actions",
"products.cdp",
"products.early_access_features",
"products.error_tracking",
"products.feature_flags",
"products.surveys",
"products.messaging",
"products.notifications",
# Facade-only: the Run scout action's endpoint starts a scout run through
# products.signals.backend.facade.api.
"products.signals",
# Facade-only: the AI task action's endpoint creates tasks through
# products.tasks.backend.facade.workflow_tasks.
"products.tasks", "products.access_control",
# Facade-only: the Classify with AI action's endpoint asks the model through
# products.ml_inference.backend.facade.api.
"products.ml_inference",
# Facade-only: the Replay Vision step's endpoint starts a scan through
# products.replay_vision.backend.facade.api.
"products.replay_vision",
]
layer = "modules"
# Facade + views: the canonical public surface for all isolated products.
# When `hogli lint:tach` rejects an import into a product listed here, do not widen this block or
# add an expose entry or a legacy-leak block for it. Add a function to the product's
# backend/facade/api.py that returns a contract, or a backend/facade/testing.py helper for a test.
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
"backend\\.routes.*",
]
from = [
"products\\.(autoresearch|batch_exports|canvas|customer_analytics|data_modeling|data_warehouse|demo|endpoints|error_tracking|experiments|groups|managed_warehouse|mcp_analytics|mcp_store|messaging|metrics|notebooks|notifications|streamlit_apps|subscriptions|tasks|tracing|visual_review|warehouse_sources|workflows)",
]
# TODO: Experiments-specific temporary interface
# Remove this block once all ViewSets are migrated to products/experiments/backend/presentation/views.py
[[interfaces]]
expose = [
"backend\\.presentation\\.experiments.*",
"backend\\.presentation\\.saved_metrics.*",
]
from = [
"products.experiments",
]
# Billing alerts registers product-owned workflows and a schedule with the shared billing worker.
[[interfaces]]
expose = [
"backend\\.temporal\\.ACTIVITIES",
"backend\\.temporal\\.WORKFLOWS",
"backend\\.temporal\\.schedule\\.create_schedule_due_billing_alert_checks_schedule",
]
from = [
"products.billing_alerts",
]
# Experiments is the first isolated product to register a Temporal workflow, so the canonical interface
# doesn't cover the registration surface yet. Narrowly expose the symbols core imports (worker registration,
# schedule registration, and the worker interceptor) rather than widening every isolated product's surface.
[[interfaces]]
expose = [
"backend\\.temporal\\.ACTIVITIES",
"backend\\.temporal\\.EXPERIMENT_CANARY_ACTIVITIES",
"backend\\.temporal\\.EXPERIMENT_CANARY_WORKFLOWS",
"backend\\.temporal\\.EXPERIMENT_ENROLLMENT_CENSUS_ACTIVITIES",
"backend\\.temporal\\.EXPERIMENT_ENROLLMENT_CENSUS_WORKFLOWS",
"backend\\.temporal\\.EXPERIMENT_RECALCULATION_ACTIVITY_QUEUE_ACTIVITIES",
"backend\\.temporal\\.EXPERIMENT_SCHEDULED_RECALCULATION_ACTIVITIES",
"backend\\.temporal\\.EXPERIMENT_SCHEDULED_RECALCULATION_WORKFLOWS",
"backend\\.temporal\\.WORKFLOWS",
"backend\\.temporal\\.recalculation_metrics.*",
"backend\\.temporal\\.schedule\\.create_experiment_precompute_canary_schedule",
"backend\\.temporal\\.schedule\\.create_experiment_precompute_enrollment_census_schedule",
"backend\\.temporal\\.schedule\\.create_experiment_scheduled_recalculation_schedules",
]
from = [
"products.experiments",
]
# streamlit_apps exposes the HogQL bridge view, routed directly from posthog/urls.py.
[[interfaces]]
expose = [
"backend\\.presentation\\.bridge_views.*",
]
from = [
"products.streamlit_apps",
]
# Legacy leaks — core (posthog/ee) imports these internals directly.
# Each block below should shrink as imports migrate to the facade.
# These products have had backend:contract-check removed from package.json
# so CI treats them as non-isolated until the leaks are cleaned up.
[[interfaces]]
expose = [
"backend\\.experiment_saved_metric_service.*",
"backend\\.experiment_service.*",
"backend\\.hogql_queries.*",
"backend\\.llm_metric_templates.*",
"backend\\.max_tools.*",
"backend\\.metric_utils.*",
"backend\\.models.*",
"stats\\..*",
]
from = [
"products.experiments",
]
[[interfaces]]
expose = [
"backend\\.admin.*",
"backend\\.models.*",
"backend\\.oauth.*",
"backend\\.url_policy\\.check_mcp_url_policy",
"backend\\.url_policy\\.trust_environment_proxy",
]
from = [
"products.mcp_store",
]
# isolation:permanent-interface
# warehouse_sources' HogQL direct-SQL adapters and its system-table definitions depend on a
# handful of source internals + models that the facade re-exports lazily — facade.source_management
# is a thin PEP 562 re-export, so a change to the underlying source flows through to core. That
# coupling can't be sealed by watching backend.facade alone, so these modules are declared
# permanent and turbo.json keeps them in the contract-check inputs — any change to them still
# re-runs the Django suite (IsolationChainCheck enforces that). The vendor set is exactly the
# sources core reaches through the facade, guarded by test_ci_core_coupled_sources.py. All other
# core coupling goes through backend.facade.
[[interfaces]]
expose = [
"backend\\.models.*",
"backend\\.temporal\\.data_imports\\.sources\\.common.*",
"backend\\.temporal\\.data_imports\\.sources\\.generated_configs\\.postgres.*",
"backend\\.temporal\\.data_imports\\.sources\\.google_search_console.*",
"backend\\.temporal\\.data_imports\\.sources\\.mysql.*",
"backend\\.temporal\\.data_imports\\.sources\\.postgres.*",
"backend\\.temporal\\.data_imports\\.sources\\.redshift.*",
"backend\\.temporal\\.data_imports\\.sources\\.salesforce.*",
"backend\\.temporal\\.data_imports\\.sources\\.snowflake.*",
"backend\\.temporal\\.data_imports\\.sources\\.stripe.*",
]
from = [
"products.warehouse_sources",
]
# legal_documents has a non-standard public surface — posthog/urls registers its pandadoc
# webhook (presentation.webhook), not the canonical presentation.views — so it gets a dedicated
# block rather than joining the canonical alternation above.
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation.*",
"backend\\.routes.*",
]
from = [
"products.legal_documents",
]
# isolation:permanent-interface
# error_tracking exposes its ClickHouse DDL (sql / embedding / indexed_embedding) to core's
# ClickHouse schema registry, conftest, the embedding worker, the hogql document_embeddings
# system table, and frozen ClickHouse migrations. That coupling is outside the import-reroute
# path — frozen migrations hardcode these import paths and the schema registry owns table DDL —
# so it can never route through the facade. The marker above declares it permanent rather than a
# legacy leak, so it does not hold the external seal open. turbo.json keeps these three modules
# in the contract-check inputs so any change to them still re-runs the Django suite
# (IsolationChainCheck enforces that). All other core coupling now goes through backend.facade.
[[interfaces]]
expose = [
"backend\\.embedding.*",
"backend\\.indexed_embedding.*",
"backend\\.sql.*",
]
from = [
"products.error_tracking",
]
# isolation:permanent-interface
# batch_exports exposes its ClickHouse DDL (backend.sql) to ten frozen ClickHouse migrations
# under posthog/clickhouse/migrations/, which import the view constants by dotted path. A frozen
# migration can never be rerouted, and the module holds CREATE ... VIEW constants and nothing
# else, so this coupling is outside the import-reroute path. The marker above declares it
# permanent rather than a legacy leak, so it does not hold the external seal open. turbo.json
# must keep backend/sql.py in the contract-check inputs when that narrowing lands, so a change
# to it still re-runs the Django suite (IsolationChainCheck enforces that).
[[interfaces]]
expose = [
"backend\\.sql.*",
]
from = [
"products.batch_exports",
]
[[interfaces]]
expose = [
"backend\\.facade.*",
]
from = [
"products.tracing",
]
[[interfaces]]
expose = [
"backend\\.logic.*",
]
from = [
"products.tracing",
]
# TODO: legacy leaks — posthog/temporal/mcp_analytics/* and the dashboard
# integration import models / dashboard_templates / intent_clustering directly.
# Move those into the product (and through the facade) so this interface can
# shrink back to the isolated default.
[[interfaces]]
expose = [
"backend\\.dashboard_templates.*",
"backend\\.intent_clustering.*",
"backend\\.models.*",
]
from = [
"products.mcp_analytics",
]
[[modules]]
path = "products.legal_documents"
depends_on = [
"ee",
"posthog",
]
layer = "modules"
[[modules]]
path = "products.access_control"
depends_on = [
"posthog",
"ee",
"products.event_definitions",
]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
# Shared RBAC viewset/serializer mixins: DRF code cannot live in facade.*, so external
# consumers import these from presentation instead
"backend\\.presentation\\.access_control.*",
"backend\\.presentation\\.views.*",
"backend\\.routes.*",
]
from = [
"products.access_control",
]
# Legacy leaks — core (posthog/ee) still imports these internals directly.
[[interfaces]]
expose = [
"backend\\.models.*",
"backend\\.property_access_control.*",
]
from = [
"products.access_control",
]
# Legacy leaks — core still runs the insight-alert pipeline itself: the alert check tasks
# under posthog/tasks/alerts/, the Temporal workflows under posthog/temporal/alerts/ and
# posthog/temporal/ai/anomaly_investigation/, and the PostHog AI product-analytics preset
# reach the alert models, the insight evaluation, the insight lifecycle adapter, the
# investigation episode reader and the Max tool directly. The exit is moving that pipeline
# into the product; the block drains as it moves, and keeps backend:contract-check off
# until it is empty.
# backend.models has a second exit of its own: pulse, dashboards, signals, the AI entity
# search and posthog/models/organization.py import it from production code outside core, so
# moving the pipeline does not by itself retire that line.
# tach interfaces have no consumer scoping, so this block opens the listed paths to every
# module, not only to the consumers named above.
[[interfaces]]
expose = [
"backend\\.evaluation",
"backend\\.evaluation\\.check_alert_for_insight",
"backend\\.evaluation\\.contract.*",
"backend\\.evaluation\\.detector.*",
"backend\\.evaluation\\.dispatcher.*",
"backend\\.evaluation\\.validation.*",
"backend\\.insight_alert_state_machine.*",
"backend\\.investigation_episode.*",
"backend\\.max_tools.*",
"backend\\.models.*",
]
from = [
"products.alerts",
]
# Legacy leaks — core (posthog/ee), customer_analytics, reminders and signals still
# import the Notebook ORM directly: file-system / search / reminders registries dispatch
# on the model class, middleware returns a raw queryset, the read_data and notebook-context
# Max tools need the instance for access-control checks, sharing re-fetches it for the
# shared-export serializer, and customer_analytics owns a CRUD-over-Notebook surface that
# moves in its own presentation wave. Shrinks as those migrate behind the facade; keeps
# backend:contract-check off until empty.
[[interfaces]]
expose = [
"backend\\.models.*",
]
from = [
"products.notebooks",
]
[[modules]]
path = "products.business_knowledge"
depends_on = ["posthog", "ee", "products.access_control", "products.tasks"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.api.*",
"backend\\.logic.*",
"backend\\.constants.*",
"backend\\.models.*",
"backend\\.temporal.*",
# Other products register LearningEvidenceProvider here. BK never imports them.
"backend\\.learning\\.contracts.*",
"backend\\.learning\\.providers.*",
]
from = [
"products.business_knowledge",
]
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.routes.*",
]
from = [
"products.business_knowledge",
]
[[modules]]
path = "products.wizard"
depends_on = ["posthog", "products.event_definitions", "products.security", "products.tasks", "products.warehouse_sources"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.logic\\.event_property_purge.*",
"backend\\.logic\\.placeholder.*",
"backend\\.models.*",
"backend\\.property_type.*",
]
from = [
"products.event_definitions",
]
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
"backend\\.routes.*",
]
from = [
"products.wizard",
]
[[modules]]
path = "products.engineering_analytics"
depends_on = ["posthog", "products.data_modeling", "products.warehouse_sources", "products.signals", "products.access_control"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
"backend\\.routes.*",
]
from = [
"products.engineering_analytics",
]
[[modules]]
path = "products.exports"
depends_on = ["posthog", "products.actions", "products.dashboards", "products.product_analytics", "products.access_control"]
layer = "modules"
[[modules]]
path = "products.annotations"
depends_on = ["posthog", "products.product_analytics", "products.dashboards", "products.error_tracking"]
layer = "modules"
[[modules]]
path = "products.field_notes"
depends_on = ["posthog"]
layer = "modules"
# Isolation debt: outside test suites build tasks rows and read its enums straight
# from the models. Drain by adding factories to facade/testing.py and migrating the
# suites, then delete this block. Consumers: canvas, slack_app, review_hog,
# replay_vision, posthog_ai, stamphog, conversations, signals, ee.
[[interfaces]]
expose = [
"backend\\.models\\.(Channel|SandboxEnvironment|Task|TaskRun|TaskThreadMessage)",
]
from = [
"products.tasks",
]
[[modules]]
path = "products.stamphog"
depends_on = ["posthog", "products.tasks"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.stamphog",
]
[[modules]]
path = "products.data_catalog"
depends_on = [
"posthog",
"products.data_quality",
"products.data_tools",
"products.product_analytics",
"products.warehouse_sources",
"products.data_modeling", "products.access_control",
]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.data_catalog",
]
[[modules]]
path = "products.apm"
depends_on = ["posthog"]
layer = "modules"
[[modules]]
path = "products.data_quality"
depends_on = [
"posthog",
"products.data_catalog",
"products.data_modeling",
"products.notifications",
"products.warehouse_sources", "products.access_control",
]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.apm",
]
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.serializers.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.data_quality",
]
[[modules]]
path = "products.ai_training"
depends_on = ["posthog"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.ai_training",
]
[[modules]]
path = "products.ml_inference"
depends_on = ["posthog"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.ml_inference",
]
[[modules]]
path = "products.security"
depends_on = ["posthog"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
"backend\\.presentation\\.hub_api.*",
]
from = [
"products.security",
]
[[modules]]
path = "products.today"
depends_on = [
"ee",
"posthog",
"products.access_control",
"products.alerts",
"products.conversations",
"products.customer_analytics",
"products.dashboards",
"products.data_catalog",
"products.error_tracking",
"products.product_analytics",
"products.signals",
"products.tasks",
"products.web_analytics",
"products.workflows",
]
layer = "modules"
[[modules]]
path = "products.alerts_platform"
depends_on = ["posthog", "products.access_control", "products.notifications"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.alerts_platform",
"products.today",
]
[[interfaces]]
expose = [
"backend\\.facade.*",
]
from = [
"products.ai_observability",
]
# Legacy leaks: modules other code imports from ai_observability today. Drain them into the facade over time.
[[interfaces]]
expose = [
"backend\\.activity_logging.*",
"backend\\.ai_event_lookup.*",
"backend\\.api.*",
"backend\\.backfill_candidates.*",
"backend\\.dashboard_templates.*",
"backend\\.dataset_queries.*",
"backend\\.dataset_service.*",
"backend\\.hog.*",
"backend\\.llm.*",
"backend\\.markdown_outline.*",
"backend\\.models.*",
"backend\\.prompt_references.*",
"backend\\.queries.*",
"backend\\.summarization.*",
"backend\\.text_repr.*",
"backend\\.tools.*",
]
from = [
"products.ai_observability",
]
[[modules]]
path = "products.webmcp"
depends_on = ["posthog"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.webmcp",
]
[[modules]]
path = "products.cross_project_dashboards"
depends_on = ["posthog", "products.access_control", "products.product_analytics"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.cross_project_dashboards",
]
[[modules]]
path = "products.warehouse_suggestions"
depends_on = [
"posthog",
"products.access_control",
"products.data_catalog",
"products.data_modeling",
"products.warehouse_sources",
]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.warehouse_suggestions",
]
[[modules]]
path = "products.commands"
# Facade-only (enforced by each product's [[interfaces]] block): commands dispatch into these
# products.
depends_on = [
"posthog",
"products.error_tracking",
"products.review_hog",
"products.security",
"products.stamphog",
"products.tasks",
]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
]
from = [
"products.commands",
]
[[modules]]
path = "products.account_audits"
depends_on = ["posthog", "products.notebooks", "products.signals", "products.skills", "products.tasks"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.account_audits",
]
[[modules]]
path = "products.ai_gateway"
depends_on = ["posthog"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.ai_gateway",
]
[[modules]]
path = "products.uptime_monitoring"
depends_on = ["posthog"]
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
"backend\\.presentation\\.views.*",
]
from = [
"products.uptime_monitoring",
]
[[modules]]
path = "products.authorization"
depends_on = []
layer = "modules"
[[interfaces]]
expose = [
"backend\\.facade.*",
]
from = [
"products.authorization",
]