feat(configstore): replace org default_team_id with duckgres_org_teams#969
Merged
Conversation
An org previously mapped to exactly one PostHog team via the
default_team_id column on duckgres_orgs. Managed warehouses need to
serve many teams per org, so migration 000024 introduces
duckgres_org_teams: one row per (org, team) carrying the warehouse
schema the team's data lives in ("team_<id>"), a per-team enabled
switch, a tri-state backfill_enabled flag, and an is_billing_team mark
constrained to at most one row per org by a partial unique index. The
migration backfills one billing row per org from default_team_id and
drops the column; Down restores the (nullable) column from the billing
row.
Billing keeps its contract, keyed off the billing team instead of the
column:
- ConfigStore.OrgDefaultTeamID becomes OrgBillingTeamID (same
0-when-missing contract); the snapshot loads Teams alongside
Users/Warehouse, so reads stay snapshot-backed.
- The compute meter and storage sampler resolve the billing team.
- Provisioning inserts/repoints the org's billing team row
(SetOrgBillingTeamTx: demote the old row, upsert the new one) and
re-attributes unacked usage buckets in the same transaction, exactly
as the column update used to.
- The admin API keeps the default_team_id JSON field for wire/UI
compat: Org.DefaultTeamID is now a gorm:"-" view populated from the
preloaded Teams association on reads and translated into the
billing-team upsert on org create/update. Clearing stays rejected;
a changed value still re-attributes usage in the same transaction.
Seeds (local/kind/tenant-isolation) insert the team row instead of the
column. Migration tests cover the new table, the legacy-schema
backfill, the v8-upgrade replay, and the GORM-model/goose-schema
metadata match for duckgres_org_teams (including the partial unique
index and the CASCADE FK).
Test Impact PlanDeterministic summary of how this PR changes tests, CI runners, and coverage-risk signals. Summary
Signals
Coverage risk: neutral or increased No coverage-reduction warnings detected. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An org maps to exactly one PostHog team via the
default_team_idcolumn onduckgres_orgs. Managed warehouses need to serve many teams per org (multi-environment orgs), and per-team state — which warehouse schema a team's data lives in, whether the team is enabled, whether its backfill runs — has no home in the config store.Change
Migration
000024introducesduckgres_org_teams: one row per (org, team) withschema_name(required) — the warehouse schema the team's data lives in (team_<id>convention)enabled(NOT NULL, default TRUE) — per-team serving switchis_billing_team(nullable) — the team pull-based billing keys the org's usage buckets by; a partial unique index allows at most one billing row per orgbackfill_enabled(nullable, tri-state)The migration backfills one billing row per org from
default_team_id(team_<id>schema) and drops the column. Down restores the column (nullable) from the billing row.Billing keeps its contract, keyed off the billing team
ConfigStore.OrgDefaultTeamID→OrgBillingTeamID, same 0-when-missing contract; the snapshot loads Teams alongside Users/Warehouse so reads stay snapshot-backedSetOrgBillingTeamTx: demote-then-upsert, ordered for the immediate partial-index check) and re-attributes unacked usage buckets in the same transaction, exactly as the column update used toAPI compat
The admin/provisioning wire field stays
default_team_id:Org.DefaultTeamIDis now agorm:"-"view populated from the preloaded Teams association on reads, translated into the billing-team upsert on writes. Admin UI and e2e payloads unchanged (TS types extended withOrgTeam). Clearing is still rejected.Tests
TestOrgBillingTeamIDsnapshot lookup (billing row → id, absent → 0, nil snapshot → 0)go build ./...,go vet ./...(+-tags kubernetes),go test ./controlplane/..., admin + configstore suites against a real DB, admin UI tsc + vitest. The live-k8s e2e harness was not run (needs a cluster); its payloads use the unchanged wire field.