Skip to main content

Task Breakdown: Team Owner Field & Team Permission — Backend

Mode: real-ticket coverage mapping, not a speculative breakdown. Tickets already exist under TF-3185, created manually ahead of this doc. This is a reconciliation of the RFC's Detail 4.D backend chunks (1–7, 11) against the actual backend-labeled tickets, so the repo has a record of what's covered, what's a gap, and what's scope beyond the RFC. No effort estimates — tickets are already sized and mostly built.

Scope

Backend only (label: backend under TF-3185). FE (chunks 8–9), Mobile (chunk 10), and QA tickets are out of scope for this doc.

Coverage verdict

All 12 in-RFC backend chunks are covered by an existing ticket. The migration backfill job (chunk 11 / CHG-004) is covered by TF-3564, split from its own Story TEAM-S20 (TF-3563) — see 2026-07-22 update below. Three additional tickets (TEAM-S18, TEAM-S19-NEG, TEAM-S20) exist in Jira but sit outside the RFC's traced 17-story scope; see Scope additions beyond the RFC.

Chunk → ticket mapping (RFC Detail 4.D, BE chunks)

#RFC chunkFiles (per RFC)TicketStatusCoverage
1Add team_owner_ids field + index migrationcontact/base.go; db/migrations/033_*.jsonTF-3448 — Adding a new field team_owners on Collection ContactsReady to Test✅ Covered
2List filter $in (bypass $or expansion)search_contact_request.goTF-3450customers_customers_view enforces TEAM ONLY (TEAM-S02)Ready to Test✅ Covered (bundled into the S02 enforcement ticket)
3Extend EvaluatePermissions + team branchpermission_service.goNo standalone ticket — inferred to be implemented as shared logic inside TF-3450 (first enforcement ticket) and reused by 3451–3453Ready to Test (inferred)⚠️ Likely covered, not separately ticketed — worth a quick code check that EvaluatePermissions was actually extended once, not duplicated per-guard
46 handler guards + single-get + assoc/deletecontact_handler.go; get_contact.goTF-3450 (view/get), TF-3451 (manage), TF-3453 (delete), TF-3452 (searchassoc)Ready to Test✅ Covered — one ticket per guarded action, matching TEAM-S02/S04/S06/S08
5Write validation (team_owner_ids ⊆ viewer teams)contact_sync_request.go; handlerTF-3454 — USER sets Team Owner multi-select (TEAM-S10)Ready to Test✅ Covered
6Fallback + events + flag gatepermission_service.go; middleware/configTF-3488 — Guard team-only level behind enabled_team_permission flagReady to Test⚠️ Partially covered — ticket description only names the flag gate ("if true, team-only works; if false, treated as everything"). It does not mention the Launchpad-outage fallback-to-OWNED-ONLY behavior or the cdp_team_permission_fallback / cdp_team_permission_denied events from Decision 2 / §3 Monitoring. Recommend confirming in code/PR whether these landed here, in TF-3449, or nowhere.
7Notes parent-scope projection + service eval + resolveNotePermission team branchcontact_notes/*; contact_notes_service.go; contact_notes_handler.goTF-3455 (view, S12), TF-3456 (manage, S14), TF-3457 (delete, S16)Ready to Test✅ Covered
Teams-for-user(+descendants) LP client + cache (Decision 4, §2.4)new HTTP client + Redis cacheTF-3449 — Integration between LP service and CDP service (consumes GET /private/teams)Ready to Test✅ Covered — not a numbered Detail 4.D chunk but the dependency the RFC calls out as blocking; ticket path note: description says GET /private/teams, RFC §2.4 pins GET /private/users/{user_sso_id}/teams?include_descendants=true — confirm the implemented path matches the RFC contract, not just team info in general
11Migration backfill (CHG-004) — CDP-owned one-off jobnew job reading crm_people.crm_team_hierarchy_id → name-map → team_owner_idsTF-3564 — Migration backfill job, split from Story TEAM-S20To Do✅ Covered — see note below

Gap: migration backfill job (chunk 11 / CHG-004) — closed

No ticket under TF-3185 originally covered the CDP-owned backfill job that migrates existing contacts' legacy crm_team_hierarchy_id into team_owner_ids. This was not a minor item — the RFC's own success criteria require ≥95% of contacts to have a non-empty team_owner_ids after migration (§1 Success Criteria #5), and the RFC review's Priority Actions for GA explicitly call out running this backfill "on a QA tenant before per-tenant enable." Without it, existing contacts stay Unassigned (visible to everyone under TEAM ONLY) indefinitely, which undermines the whole feature for pre-existing data.

Closed 2026-07-21: created TF-3548 (since superseded — see below), parented to Epic TF-3185, labeled backend, status To Do.

2026-07-22 update — backfill Story and Task recreated

TF-3548 was created story-less (no owning Story, by design — chunk 11 isn't one of the RFC's traced 17 stories). A jira-alignment-check pass flagged this as worth a decision one way or the other rather than leaving it an implicit exception. Decision: give the backfill its own Story-level tracking, same pattern as TEAM-S18/TEAM-S19-NEG below. Full rationale in delivery/decisions/0002-separate-migration-backfill-story-and-task.md.

  • TF-3548 closed Won't Fix (2026-07-22) — no functional change, purely a traceability/ownership correction. Comment on the ticket records the supersession.
  • TF-3563 — new Story, TEAM-S20, "Migrate existing contacts to team_owner_ids via one-off backfill job (CHG-004)".
  • TF-3564 — new Task, same scope/AC as TF-3548, parented to Epic TF-3185 and split from TF-3563. Verified correct Work item split direction (Task renders "split from" Story).

Scope additions beyond the RFC

Three Jira items exist under TF-3185 that are not part of the RFC's traced 17 stories (TEAM-S01…TEAM-S17, Detail 1.C):

  • TF-3458 / TEAM-S18 — "Team Owner auto-syncs to the new owner's teams when contact ownership changes" (labeled backend, Ready to Test).
  • TF-3447 / TEAM-S19-NEG — "TEAM ONLY guard rails (no leakage / correct hierarchy + Unassigned)" (Story, no backend label, To Do).
  • TF-3563 / TEAM-S20 — "Migrate existing contacts to team_owner_ids via one-off backfill job (CHG-004)" (Story, To Do) — the backfill's own Story, split from by Task TF-3564. See 2026-07-22 update above.

Per product direction, these are a deliberate post-RFC scope amendment: TEAM-S18 adds live re-derivation of team_owner_ids on ownership change, which the RFC's Decision 1 explicitly did not choose (team_owner_ids is a create-time snapshot — OQ-2 was resolved in favor of snapshot semantics, not re-sync). Documenting this here so the discrepancy between the RFC's written decision and the shipped behavior is traceable rather than silent:

  • The RFC (and its R2-reviewed, PROCEED-verdict revision) does not describe or gate auto-sync-on-ownership-change behind cdp_team_permission_enabled, analytics, or a rollback path the way the other chunks are.
  • If TEAM-S18 ships, OQ-2 in the RFC's §5 should be updated to reflect the amended decision (snapshot-at-create → snapshot-with-resync-on-owner-change), and Decision 1's "Consequences" section should note the new re-sync trigger, so a future reader of the RFC isn't misled by the stale "not auto-re-derived on later saves" text.
  • TEAM-S19-NEG (guard rails / no-leakage) reads as a test/verification story rather than new functionality — likely fine as an added QA-facing story, not a functional amendment, but flagging since it's also outside Detail 1.C's numbering.

Full ticket ↔ story cross-check (backend-labeled only)

TicketStory IDIn RFC's 17 stories?RFC chunk
TF-3448TEAM-S01 (field)Yes1
TF-3449TEAM-S01 (LP integration)Yes (dependency, §1/§2.4)n/a (Decision 4)
TF-3450TEAM-S02Yes2, 3 (shared), 4
TF-3451TEAM-S04Yes4
TF-3452TEAM-S08Yes4
TF-3453TEAM-S06Yes4
TF-3454TEAM-S10Yes5
TF-3455TEAM-S12Yes7
TF-3456TEAM-S14Yes7
TF-3457TEAM-S16Yes7
TF-3488TEAM-S01 (flag)Yes (CHG-005)6 (partial — see gap note)
TF-3458TEAM-S18No — post-RFC amendmentn/a
TF-3564TEAM-S20No — post-RFC amendment11

Note: TEAM-S03/S05/S07/S09/S11/S13/S15/S17 are the mobile counterparts of the stories above — they exist in Jira as Story type (TF-3438…TF-3445) with no backend label, correctly out of scope for this backend breakdown.

Summary

Count
RFC backend chunks (1–7, 11) + named LP dependency9
Covered by an existing ticket9 (chunk 11 covered by TF-3564, split from Story TEAM-S20; supersedes the closed TF-3548)
Gap (no ticket)0
Partially covered, needs confirmation2 — chunk 3 (shared EvaluatePermissions, likely bundled), chunk 6 (fallback/events not evidenced in ticket text)
Tickets beyond RFC scope3 — TF-3458 (TEAM-S18), TF-3447 (TEAM-S19-NEG), TF-3563/TF-3564 (TEAM-S20)

Bottom line: the manual breakdown is now complete for all in-RFC backend chunks — every enforcement point (view/manage/delete/searchassoc/notes), the core data model change, and the migration backfill job (TF-3564, split from Story TEAM-S20 / TF-3563; supersedes the closed TF-3548) are ticketed. Remaining follow-ups are non-blocking confirmations: the fallback/events piece of chunk 6 is worth a quick look in the actual PR/code to confirm it's not silently missing, and TF-3458's RFC amendment should be reflected back into the RFC's OQ-2/Decision 1 text.