Files
zeekay 43760f5c0e cloud is the one authority that mints an ingest key
The ingest door resolves the key it is handed to an (org, project) before it
accepts a row, so a key this deployment invents resolves to nothing and every
event is refused with ingest_key_unknown. A team holding one looks configured
and drops everything it is sent.

So insights stops making them and holds the one cloud made. `mint` refuses
KeyKind.PUBLISHABLE; `insights/ingest.py` asks cloud at POST /v1/projects with
the acting person's IAM bearer, reading an existing project's key back on 409 so
a re-run of the login pipeline lands on the same key. The bearer is refreshed
through social-auth when it is past its own exp -- measured, three of four stored
tokens were expired by 6-10 days -- and there is deliberately no service-identity
fallback, because that token carries a different owner.

The key reader could not read most of what cloud mints: cloud encodes 32 bytes
as base64url and the tail alphabet was base57, which dropped three of the four
real keys now in production. The tail is base64url now.

Two callers borrowed the ingest minter for things that are not ingest keys. The
conversations widget token gets its own kind and mark; the demo matrix stops
re-randomising a token it cannot replace. Resetting an ingest key is removed end
to end -- cloud has no rotation route, so a reset could only produce a key the
door refuses -- and with it a Vercel notification whose import has not existed
for some time and raised on every api_token change.
2026-08-08 20:47:28 -07:00
..

Team model and extensions

This directory contains the core Team model and its extension models.

Why this matters: availability, not just hygiene

insights_team is read on virtually every request, so any ALTER TABLE on it — even adding a nullable column, which is metadata-only — needs an ACCESS EXCLUSIVE lock and can stall site-wide traffic while it waits in the lock queue behind in-flight queries. This has caused production 5xx incidents. An extension model only does a CREATE TABLE, which takes no lock on insights_team at all.

The migration analyzer (HotTableAlterPolicy) blocks unacknowledged DDL on insights_team in CI; fields that genuinely belong on Team need an entry in insights/management/migration_analysis/hot_table_acknowledged_migrations.txt. See the Altering Hot Tables guide.

When to add fields to Team vs create an extension

Add to Team directly when the field is:

  • Core team identity (name, API tokens, timezone)
  • Cross-product settings (test account filters, path cleaning)
  • SDK configuration that affects multiple products

Create an extension when the field is:

  • Domain-specific configuration for a single product
  • Part of a larger config object that grows together
  • Settings that most teams won't use

Recent additions that should have been extensions include toggles for Experiments, Conversations, Session Recording, etc. Going forward, product teams should use extension models for their domain-specific configuration.

Creating a new Team extension

# products/my_product/backend/models/team_my_product_config.py
import logging

from django.db import models

from insights.models.team import Team
from insights.models.team.extensions import register_team_extension_signal

logger = logging.getLogger(__name__)


class TeamMyProductConfig(models.Model):
    # Use Team's primary key as this model's primary key
    team = models.OneToOneField(Team, on_delete=models.CASCADE, primary_key=True)

    # Your domain-specific fields
    some_setting = models.BooleanField(default=False)
    config_json = models.JSONField(default=dict)


register_team_extension_signal(
    TeamMyProductConfig,
    defaults={"some_setting": True},  # optional
    logger=logger,
)

Then run python manage.py makemigrations.

Usage

Access the extension via the helper — do not add accessors to the Team model:

from insights.models.team.extensions import get_or_create_team_extension
from .models.team_my_product_config import TeamMyProductConfig

config = get_or_create_team_extension(team, TeamMyProductConfig)

Some older extensions still have team.<product>_config descriptors on the Team model. These are transitional and should not be used as a pattern for new extensions.