ADR-0017: Supervisor Team Scope Resolved by Scanning the Shared Member Table

Status

Accepted

Tags

fieldforce, teams, supervisor, better-auth, adr-0006

Decision

Fieldforce supervisor scope (which tasks a supervisor can see) is resolved by reading the supervisor’s team_ids from the shared member table (a JSONB column after the prerequisite migration), then querying all members in the org and filtering to those sharing at least one team ID. This runs in the Go HTTP handler on every list request for a supervisor-role caller. No team data is replicated into the Go schema.

Why

Teams are owned by the Bun backend. The member table is part of the Better Auth schema — read-only from Go (ADR-0006). There is no teams join table accessible from Go. Replicating team membership into a Go-owned table requires a sync mechanism between Bun and Go that does not exist and violates the bounded context boundary. Rejected alternatives:
  • Replicate teams into a Go-owned table: Violates ADR-0006. Requires ongoing synchronization with no existing infrastructure. Rejected.
  • Dedicated teams API endpoint in Go: Phase 1 scope. Revisit in Phase 4 if org sizes exceed thousands of members. Rejected for Phase 1.

How it works

Index (added in prerequisite migration):
team_ids is JSONB after the prerequisite migration (Task 0 in Phase 1). The @> containment operator uses the GIN index for efficient lookup. For orgs with hundreds of members, the O(n) scan is one indexed query — acceptable.

Known limitations

  • Scale threshold: At > 1,000 members per org, the full-member scan may degrade beyond 200ms P95. At > 200ms P95 on supervisor list requests, revisit with a Go-side teams cache or a dedicated teams service.
  • Stale team membership: If a member is added to or removed from a team in Bun, the change is reflected on the next Go request — no cache invalidation needed because Go always reads the current member table.

Rules for agents

  • Never write to the member table from Go — SELECT only (ADR-0006)
  • resolveAllowedUserIDs must return nil (no filtering) for non-supervisor roles — nil means “see all org tasks”
  • TaskFilter.AllowedUserIDs non-nil means “restrict to this set” — the repository applies AND user_id = ANY(?) on the assignments join
  • Do not cache the result of resolveAllowedUserIDs in application memory — team membership changes must be reflected immediately

Bad pattern (do not generate)

Good pattern