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.
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.