Scope everything to a team, and route alerts by integration key #11
Reference in New Issue
Block a user
Delete Branch "teams"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The core of #4. Stacked on #10 — based on
admin-role, so merge that first and this retargets tomain.terdut stops being one shared space. A team owns its incidents, alerts, schedule and integrations; a user sees exactly the teams they are in. Everything that exists moves into one Default team with every current user as an owner, so the upgrade is a no-op for the people using it.
Ingestion is the load-bearing half
An alert arrives on a team's integration key, and the key is both the credential and the routing. That also closes the unauthenticated webhook — the old path stays for one release, deprecated and routed to the oldest team, so an upgrade doesn't stop delivering while the Alertmanager config is edited.
Scoping is enforced in few places, because the failure mode is silent
serveAsloads the caller's memberships once per request.team_id = ANY(...).incidentIDParam, which now parses the id and checks the team in the same call — so a new handler can't remember the first half and forget the second.404, never403: whether an incident exists is that team's business.Two bugs this surfaced, both silent
upsertAlertsdecided "is this a new occurrence" from the fingerprint alone. Across teams that made team B's first alert look like a re-send of team A's, so it opened no incident at all. Now keyed on(team_id, fingerprint), as the index is.team_id.Roles
Team roles are a separate axis from the system administrator flag. An owner configures the team, a member works its incidents, and an admin is not implicitly in every team — administration is about accounts, not about reading other people's incidents. An admin can still repair a team whose owner left, which is why
requireTeamOwnerlets them through.A shift can only be given to somebody in the team: paging a person who cannot open the incident is worse than paging nobody.
Verified
make fmt lint test helm-lintgreen with-raceagainst Postgres 17, plus seven new tests inteams_test.gothat build two teams and check the boundary from both sides: incidents, alerts and stats scoped; the other team's incident unreadable and unactionable by id; the same fingerprint and the same date legal in both; an unknown key rejected with401and nothing ingested; a member refused team configuration but allowed to read it; an outsider seeing nothing at all.Also exercised against a live server: bootstrap → default team → mint an integration → post an alert on its key → the incident appears with its
team_idandteam_name.Breaking for API clients
The schedule endpoints moved under
/api/teams/{teamID}/schedule, andGET /api/schedule/currentnow returns an array (one entry per team with somebody on call) rather than an object or a404. terdut-tui will need a version for that.Deliberately not here
deadman.gois configured, not who sees what.https://claude.ai/code/session_01RHPj4ggeFdEjKKfm4SHbD7