API Contract: Mention User in CDP Notes
Derived from
rfc-notes-mention-user.mdDetail 2.4 / 2.A / 1.C / 1.A, cross-referenced againstrfc-notes-mention-user.task-breakdown.mdfor task ids, and grounded against the real BE code at../../../contact-service(relative to this repo's checkout — see rootCLAUDE.mdsibling-checkout convention) and the real FE code at../../../qontak-customer-fe. This file is a projection, not a new source of truth — if a contract here needs to change, change the RFC first (a new Decision entry if it's material), then regenerate this file. Composite AC ids follow the PRD's<STORY-ID>/AC-nconvention.Grounding legend: 🟢 verified against real code (file:line cited) · 🟡 proposed by this RFC, not yet in the codebase · 🔴 conflict found between the RFC and the real code.
1. Endpoints — served
1a. contact-service (this RFC builds/extends these)
| Endpoint | Method | AuthN/AuthZ | Request schema | Response schema | Status codes | Idempotency | Owning task(s) | Covers AC(s) |
|---|---|---|---|---|---|---|---|---|
/iag/v1/contacts/{contact_id}/notes | POST | RequirePermissionMiddleware + customers_customernotes_add; owner from X-Authenticated-Userid | 🟢 CreateContactNoteRequest{note, attachments?} (payload/contact_notes_request.go:8-11) | 🟢 CreateContactNoteResponse{contact_note_id, created_at, updated_at} (:33-37) 🟡 + mentioned_user_ids, mentioned_users (proposed) | 200, 400, 401, 403, 422 (TOO_MANY_MENTIONS/NOTE_TOO_LONG), 500 | none (create) | BE: 2.1, 2.2, 2.3, 2.4, 2.5 · FE (writes anchor): 1.1 · FE (reads response): 2.7 | S01/AC-1..AC-4, ERR-1, ERR-2; S02/AC-1..AC-4, ERR-1; S04/AC-1, ERR-1..ERR-3; S06-NEG/NEG-1, NEG-2 |
/iag/v1/contacts/{contact_id}/notes/{contact_note_id} | PUT | + customers_customernotes_manage; owner-only | 🟢 UpdateContactNoteRequest{note, attachments?} (:14-17) | 🟢 ContactNoteResponse{id, contact_id, note, attachments, owner_id, owner_name, permission, created_at, updated_at} (:20-30) 🟡 + mentioned_user_ids, mentioned_users (proposed) | 200, 400, 401, 403, 404, 422, 500 | last-write-wins (no version field) | BE: 2.1, 2.2, 2.3, 2.4, 2.5 · FE: 1.1, 2.7 | same as POST plus S01/AC-4, S02/AC-4, S03/AC-3 (edit-path idempotent notify) |
/iag/v1/contacts/{contact_id}/notes | GET | + customers_customernotes_view | 🟢 GetContactNotesRequest{page, per_page, order_by, order_direction, owner_ids?} (:40-47) | 🟢 GetContactNotesResponse{notes: ContactNoteResponse[], meta: PaginationMeta} (:50-61) 🟡 each note item + mentioned_user_ids, mentioned_users (proposed) | 200, 401, 403, 500 | n/a | n/a — reused, unchanged. FE render consumer: 1.2 | S01/AC-2 (chip renders from the anchor this endpoint returns) |
Note path reality (RFC-verified): the backend serves both the canonical
/iag/v1/contacts/{contact_id}/notesand a deprecated/iag/v1/contacts/notes/{contact_id}group, routed to the same handler (RFC OQ-15) — a response-shape change here appears on both automatically. No per-group work needed.
1b. Launchpad (platform-owned; consumed FE-direct or BE-relayed, not built by this RFC)
| Endpoint | Method | AuthN/AuthZ | Request schema | Response schema | Status codes | Owning task(s) | Covers AC(s) |
|---|---|---|---|---|---|---|---|
| Company user list (typeahead) | GET | session auth | 🟢 GET {CUSTOMER_360_URL}/v1/users?order_by=&order_direction=&page=&per_page=&statuses=active (FE-direct, qontak-customer-fe/.../UserStore.ts:100) — not a direct call to Launchpad's own /private/users | 🟢 { data: [{ Pagination: {page,per_page,total}, UserResponse: User[] }] }, User{id,email,full_name,status,current_sign_in_at,sso_id,created_at,user_access,role} (UserStore.ts:11-19) — confirmed no avatar field | 200, 5xx | FE: 1.1 (mocked) → 2.7 (real, client-side filter) | S01/AC-1, ERR-1, ERR-2 |
| Single-user detail (preview card) | GET | session auth (BE-relayed via contact-service's own Launchpad client) | 🟢 GET {Launchpad root}/private/users/get_by_sso_id?sso_id={uuid} (contact-service/internal/app/api/qontak_launchpad.go:223-238) | 🟢 LaunchpadUserDetailResponse{full_name,email,sso_id,nik,phone,role_crs_id,staff_level,status,external_company_id,unified_app} (qontak_launchpad.go:545-556) — confirmed no teams[], no avatar (matches OQ-22); has more real fields than the RFC's simplified subset (see §4.3) | 200, 404 (generic — OQ-20), 5xx | FE: 1.4 (mocked) → 2.8 (real, session-cached) | S05/AC-1, AC-2, ERR-1, ERR-2 |
Bulk user names lookup (mentioned_users email snapshot, Decision 15) | (bulk, exact HTTP verb/path not yet re-grounded here) | service auth (BE-internal — contact-service's own Launchpad client) | 🟡 IUserService.GetUserNamesBulk(ctx, ssoIDs) — an already-existing client method per the RFC's OQ-13 research, distinct from GetUsersByUserSsoIds (used by GetActiveUsersBySsoIds, §7); called in addition to that active-membership check because it alone returns only SsoID+Status, no email | 🟡 []{sso_id, full_name, email} (per OQ-13; exact struct/file:line not cited in the RFC — flag for re-grounding, see §8) | assume 200, 5xx (unverified) | BE: 2.5 | S01/AC-3 (mentioned_users persisted alongside mentioned_user_ids) |
2. Endpoints — depended on (outbound calls contact-service makes)
| Call | Method | Auth | Request (per the RFC) | Timeout | Retry | Failure behavior | Owning task(s) | Covers AC(s) |
|---|---|---|---|---|---|---|---|---|
POST /api/v1/notifications/crm (notification-service) | POST | X-Api-Key (service, constant-time compared) | 🟡 proposed — see §4.4 (net-new client, no notification_client.go exists in contact-service today — confirmed by repo search) | 10s | 3× exponential backoff (1,4,16,64s per the worker's backoff sequence) | log cdp_note_mention_notify_failed; note stays saved (never blocks the write) | BE: 2.6 | S03/AC-1, AC-2, AC-2b, AC-3, ERR-1, ERR-2 |
3. Inbound webhooks
N/A — no inbound webhook. (verbatim from the RFC — notification dispatch is outbound only;
mark-as-read and rendering are owned by the Notification Center / mobile app, not contact-service.)
4. Request/Response JSON contract
Every example below is either 🟢 shaped exactly like the real, currently-shipping payload in
contact-service / qontak-customer-fe (mention-related fields aside, since those don't exist
yet), or 🟡 explicitly marked proposed where this RFC adds something net-new. Grounding citations
(file:line) are kept so the shapes are checkable, but the source struct/interface definitions
themselves are not reproduced here — this is a contract, not a technical design doc. The response
envelope (resp_code/resp_desc/meta/data) is real, taken from internal/pkg/http/response.go:1-50
and internal/pkg/http/handler.go:26-29 — the RFC's own Detail 2.4 table omitted this envelope and
showed only the inner data payload; don't skip it when implementing.
4.1 POST /iag/v1/contacts/{contact_id}/notes
🟢 grounded against payload/contact_notes_request.go:8-11 (request) and :33-37 (response) +
internal/app/repository/contact_notes/base.go:17-23 (Attachment shape).
Request
{
"note": "<p>Following up with the client. cc <span data-type=\"mention\" data-id=\"3f2504e0-4f89-41d3-9a0c-0305e82c3301\" data-label=\"Budi Santoso\">@Budi Santoso</span></p>",
"attachments": [
{
"url": "https://cdn.qontak.com/notes/attachments/abc123.pdf",
"type": "pdf",
"file_size_in_byte": 204800,
"file_size": "200 KB",
"file_name": "proposal.pdf"
}
]
}
Response (mentioned_user_ids 🟡 proposed — Decision 1;
mentioned_users 🟡 proposed — Decision 15; not yet on the real CreateContactNoteResponse,
which today returns only contact_note_id/created_at/updated_at. A mention dropped for being
invalid/cross-company (Decision 5) is silent — logged server-side only, not returned here.)
{
"resp_code": "200",
"resp_desc": { "id": "OK", "en": "OK" },
"meta": { "version": "d2b6e92", "api_env": "production", "trace_id": "6a476a000000000085019557e6ec6082" },
"data": {
"contact_note_id": "66f1a2b3c4d5e6f7a8b9c0d1",
"created_at": "2026-07-09T10:15:00+07:00",
"updated_at": "2026-07-09T10:15:00+07:00",
"mentioned_user_ids": ["3f2504e0-4f89-41d3-9a0c-0305e82c3301"],
"mentioned_users": [
{ "id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301", "email": "budi.santoso@qontak.com" }
]
}
}
4.2 PUT /iag/v1/contacts/{contact_id}/notes/{contact_note_id}
🟢 grounded against payload/contact_notes_request.go:14-17 (request, identical shape to POST) and
:20-30 (response — full ContactNoteResponse, real fields).
Request (editing the note to add a second mention)
{
"note": "<p>Following up with the client. cc <span data-type=\"mention\" data-id=\"3f2504e0-4f89-41d3-9a0c-0305e82c3301\" data-label=\"Budi Santoso\">@Budi Santoso</span> and <span data-type=\"mention\" data-id=\"7c1a9e22-6b3d-4a10-9e5f-1122334455aa\" data-label=\"Siti Rahayu\">@Siti Rahayu</span></p>",
"attachments": []
}
Response (mentioned_user_ids 🟡 proposed — Decision 1;
mentioned_users 🟡 proposed — Decision 15)
{
"resp_code": "200",
"resp_desc": { "id": "OK", "en": "OK" },
"meta": { "version": "d2b6e92", "api_env": "production", "trace_id": "6a476a000000000085019557e6ec6083" },
"data": {
"id": "66f1a2b3c4d5e6f7a8b9c0d1",
"contact_id": "66e0a1b2c3d4e5f6a7b8c9d0",
"note": "<p>Following up with the client. cc <span data-type=\"mention\" data-id=\"3f2504e0-4f89-41d3-9a0c-0305e82c3301\" data-label=\"Budi Santoso\">@Budi Santoso</span></p>",
"attachments": [],
"owner_id": "9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
"owner_name": "Siti Aminah",
"permission": { "update": true, "delete": true },
"created_at": "2026-07-09T10:15:00+07:00",
"updated_at": "2026-07-09T11:02:00+07:00",
"mentioned_user_ids": ["3f2504e0-4f89-41d3-9a0c-0305e82c3301"],
"mentioned_users": [
{ "id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301", "email": "budi.santoso@qontak.com" }
]
}
}
4.3 GET /iag/v1/contacts/{contact_id}/notes
🟢 grounded against payload/contact_notes_request.go:40-47 (request query params) and :50-61
(response).
Request — contact_id comes from the URL path; everything else is a query param, no JSON body:
GET /iag/v1/contacts/66e0a1b2c3d4e5f6a7b8c9d0/notes?page=1&per_page=10&order_by=created_at&order_direction=desc
Response (mentioned_user_ids/mentioned_users on each note 🟡 proposed — Decisions 1 and 15)
{
"resp_code": "200",
"resp_desc": { "id": "OK", "en": "OK" },
"meta": { "version": "d2b6e92", "api_env": "production", "trace_id": "6a476a000000000085019557e6ec6084" },
"data": {
"notes": [
{
"id": "66f1a2b3c4d5e6f7a8b9c0d1",
"contact_id": "66e0a1b2c3d4e5f6a7b8c9d0",
"note": "<p>...cc <span data-type=\"mention\" data-id=\"3f2504e0-4f89-41d3-9a0c-0305e82c3301\" data-label=\"Budi Santoso\">@Budi Santoso</span></p>",
"attachments": [],
"owner_id": "9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
"owner_name": "Siti Aminah",
"permission": { "update": true, "delete": true },
"created_at": "2026-07-09T10:15:00+07:00",
"updated_at": "2026-07-09T11:02:00+07:00",
"mentioned_user_ids": ["3f2504e0-4f89-41d3-9a0c-0305e82c3301"],
"mentioned_users": [
{ "id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301", "email": "budi.santoso@qontak.com" }
]
}
],
"meta": { "total": 1, "limit": 10, "offset": 0, "has_more": false }
}
}
⚠️ Two different
metakeys are in play — the outer envelope'smeta(version/api_env/trace_id) and this endpoint's own pagination object nested insidedata.meta(total/limit/offset/has_more). Don't conflate them when implementing the FE client.
4.4 Launchpad — Company user list (FE-direct)
🟢 grounded against qontak-customer-fe/features/customers/store/UserStore.ts:11-19,83-129 — this
call goes straight from FE to CUSTOMER_360_URL, not through contact-service.
Request
GET {CUSTOMER_360_URL}/v1/users?order_by=created_at&order_direction=desc&page=1&per_page=50&statuses=active
Response
{
"data": [
{
"Pagination": { "page": 1, "per_page": 50, "total": 2 },
"UserResponse": [
{
"id": "usr_101",
"email": "budi.santoso@qontak.com",
"full_name": "Budi Santoso",
"status": "active",
"current_sign_in_at": "2026-07-08T09:00:00Z",
"sso_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"created_at": "2025-01-10T00:00:00Z",
"user_access": "member",
"role": "agent"
}
]
}
]
}
🔴 Resolved conflict: the previous draft of this contract flagged avatar presence as an
unresolved conflict inside the RFC (Detail 2.4/2.G said present-and-nullable; §1 Dependencies +
Decision 11 said absent). Reading the real User interface FE actually declares and consumes
settles it: there is no avatar field anywhere in this response. §1 Dependencies and Decision
11 are correct; Detail 2.4's endpoint table and Detail 2.G are wrong on this point and should be
corrected in the RFC.
4.5 Launchpad — Single-user detail (preview card)
🟢 grounded against contact-service/internal/app/api/qontak_launchpad.go:223-238 (request) and
:545-556 (response struct) + internal/app/api/response.go:18-29 (envelope).
Request
GET {Launchpad root}/private/users/get_by_sso_id?sso_id=3f2504e0-4f89-41d3-9a0c-0305e82c3301
Authorization: <service token>
Content-Type: application/json
X-Authenticated-Userid: 3f2504e0-4f89-41d3-9a0c-0305e82c3301
Response
{
"resp_code": "200",
"resp_desc": { "id": "OK", "en": "OK" },
"meta": { "version": "d2b6e92", "api_env": "production" },
"data": {
"full_name": "Budi Santoso",
"email": "budi.santoso@qontak.com",
"sso_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"nik": "3201012345670001",
"phone": "+6281234567890",
"role_crs_id": "crs_001",
"staff_level": "senior",
"status": "active",
"external_company_id": 4821,
"unified_app": true
}
}
The preview card only consumes full_name/email/staff_level/status/sso_id from this
response — the rest (nik/phone/role_crs_id/external_company_id/unified_app) are real wire
fields the RFC's simplified schema never mentioned; harmless to ignore. Confirmed: no teams, no
avatar anywhere in the real response — this matches the RFC's OQ-22 claim exactly (unlike the
avatar conflict in §4.4, this one checks out).
4.6 POST /api/v1/notifications/crm
🟡 Fully proposed — contact-service has no notification client today (confirmed: no file for
it and no reference to this endpoint path exists anywhere in the repo). Field names below are per
the RFC's claims against notification-service's handler, which is not checked out locally and
so is unverified here — treat this section as RFC-only, not code-verified, until that repo is
available for cross-check. BE-internal only (contact-service → notification-service) — no FE
consumer.
Request
{
"sso_id": "3f2504e0-4f89-41d3-9a0c-0305e82c3301",
"title": "Siti Aminah mentioned you in a note",
"description": "On contact Budi Santoso",
"notif_type": "1",
"notif_category": "2",
"click_action": "OPEN_URL",
"click_action_url": "https://app.qontak.com/customers/66e0a1b2c3d4e5f6a7b8c9d0?tab=notes",
"organization_id": "b6a8c1d2-3e4f-5061-7283-940a1b2c3d4e"
}
Auth header: X-Api-Key. No skip_fcm field exists — FCM fan-out is automatic for
crm-origin notifications (see §6 for the lock-screen PII consequence, OQ-19). No documented
response body — dispatch is fire-and-forget from contact-service's side (see §5 for the failure
path: retry×3, then log and move on).
5. Error & status code catalog
| Code | HTTP status | Trigger condition | Endpoint(s) | Emitted by | Consumed by | Covers AC(s) |
|---|---|---|---|---|---|---|
NOTE_TOO_LONG | 422 | HTML > 10,000 chars | POST/PUT .../notes | BE | FE ("Note is too long", inline) | — |
TOO_MANY_MENTIONS | 422 | > 10 distinct mentions | POST/PUT .../notes | BE | FE (inline validation) | Decision 6 |
BAD_REQUEST | 400 | malformed body | POST/PUT .../notes | BE | FE | — |
FORBIDDEN | 403 | non-owner update | PUT .../notes/{id} | BE | FE | — |
NOT_FOUND | 404 | wrong contact/company | .../notes/{id} | BE | FE | — |
cdp_note_mention_invalid (log event, not an HTTP code — soft-fail, 200) | 200 | invalid / cross-company / 404-deleted SSO id, silently dropped (Decision 5) | POST/PUT .../notes | BE | ops/audit logs only — no FE surface | S04/ERR-1, ERR-3; S06-NEG/NEG-1 |
cdp_note_mention_notify_failed (log event, not an HTTP code) | n/a — internal | notification-service 5xx/timeout, retry×3 exhausted | POST /api/v1/notifications/crm (dependency) | BE worker | ops alerting (#cdp-ops, rate > 5%) | S03/ERR-1 |
generic 404 (Launchpad) | 404 | unresolved user — deleted or non-existent, indistinguishable (OQ-20) | Company user list; Single-user detail | Launchpad | FE ("No matching people" / inactive-not-found card) | S01/ERR-2; S04/ERR-3; S05/ERR-1 |
5xx/timeout (Launchpad detail) | 5xx | lookup failure | Single-user detail | Launchpad | FE ("Couldn't load details") | S05/ERR-2 |
🟢 The BE error envelope shape (resp_code/resp_desc: {id, en}/meta) is real — internal/pkg/http/response.go:12-16.
Real error JSON:
{
"resp_code": "422",
"resp_desc": { "id": "Catatan melebihi batas 10.000 karakter", "en": "Note content exceeds maximum length of 10,000 characters" },
"meta": { "version": "d2b6e92", "api_env": "production", "trace_id": "6a476a000000000085019557e6ec6085" }
}
6. UI contract (FE)
Web
- DOM/data-attribute contract: mention span
<span data-type="mention" data-id="{sso_uuid}" data-label="{full_name}">@{full_name}</span>. DOMPurify configADD_ATTR: ['data-type','data-id','data-label']required at both save (NoteInput.vue:460) and render (NotesList.vue:104) — without it, mention identity is silently stripped. (spanis already in DOMPurify's default allow-listed tags, so noADD_TAGSis needed.) (Owning tasks: 1.1, 1.2) - State ownership: Pinia
CustomerStore(note CRUD, readsmentioned_user_ids) +UserStore(typeahead source +getUserDetail(ssoId)for the preview card — 🟢 real store confirmed atqontak-customer-fe/features/customers/store/UserStore.ts, thoughgetUserDetaildoesn't exist there yet, onlygetUsers/getTeams/getPermission); localnoteValueref inNoteInput.vue. (Owning tasks: 2.7, 2.8) - Analytics events:
cdp_note_mention_added— note created or edited with ≥1 mention. Payload:note_id, contact_id, company_sso_id, mention_count, new_mention_count, author_sso_id, event(event∈{create, edit};mention_count= total on the note,new_mention_count=|to_notify|this save).cdp_note_mention_picker_failed— typeahead/user-list call failed. Payload:company_sso_id, platform.cdp_note_mention_preview_failed(RFC-local, FE-only, S05 — not in the PRD's §11) — preview-card lookup failed (5xx/timeout). Payload:company_sso_id, sso_id, platform. (Owning task: 2.8)
- A11y: typeahead listbox
role="listbox"/option, arrow-key nav,aria-activedescendant; mention chiprole="link", accessible name@{full_name}; preview card keyboard-focusable from the chip,Esccloses.
Mobile
- Anchor emitted:
<a href="../../../users/{sso_uuid}/edit_user">@{full_name}</a>(Decision 8). (Owning task: mobile mapper change, tracked in the RFC's Detail 4.D chunk 8 — no corresponding task-breakdown id, since this breakdown covers BE + web FE only.) - Render: plain-text
@Namefallback only (Decision 7) — no chip, no preview card in v1.
7. Field-name parity check
| FE expects | BE/platform response has | Endpoint | Verdict |
|---|---|---|---|
mentioned_user_ids | mentioned_user_ids (🟡 proposed — not yet on the real ContactNoteResponse/CreateContactNoteResponse structs) | POST/PUT .../notes | ✅ shape agreed — ⚠️ not yet built: the field doesn't exist in contact-service today (confirmed by reading base.go + contact_notes_request.go); Tasks 2.1/2.5 (BE) and 2.7 (FE) add it |
mentioned_users | 🟡 proposed — Decision 15; not yet on the real structs | POST/PUT .../notes + GET (each note item) | ✅ shape agreed — ⚠️ not yet built (Task 2.5); no FE consumer scoped yet (Decision 15's own stated consequence) — available on the wire as a future display fallback for the preview card (Decision 14) if/when FE wires it up; sourced from a second Launchpad call (GetUserNamesBulk), not the active-membership call, which returns no email (OQ-13) |
data-id mention span (parser input) | composer emits it (Task 1.1) | POST/PUT .../notes | ✅ match only if DOMPurify ADD_ATTR ships on both save and render (Task 1.1/1.2) — otherwise the attribute is stripped before the parser ever sees it |
| mobile href SSO form (parser input) | mobile mapper emits id, not sso_id, until the mobile CDP-mapper change lands | POST/PUT .../notes | ⚠️ must ship together — RFC Detail 2.G flags this explicitly: until the mapper change lands, mobile emits the wrong id and the backend would store a non-SSO value |
teams (preview card) | LaunchpadUserDetailResponse (real struct, qontak_launchpad.go:545-556) has no teams[] field | Single-user detail | ⚠️ confirmed real gap, OQ-22 — verified against actual code, not just RFC prose; card ships without the teams row unless resolvable via getTeams() client-side mapping |
avatar (typeahead picker) | RESOLVED — real User interface (UserStore.ts:11-19) has no avatar field. Detail 2.4's endpoint table and Detail 2.G (which claimed it's present-and-nullable) are wrong; §1 Dependencies + Decision 11 (which claimed it's absent) are correct. | Company user list | 🔴→✅ was an open conflict, now settled by code — recommend the RFC author correct Detail 2.4/2.G, not build a null-check for a field that never arrives |
notification title/description | server-constructed only, never user note HTML | POST /api/v1/notifications/crm | ✅ verified clean — RFC Detail 2.G rates this "yes (low risk)"; no user-controlled markup reaches the notification body |
click_action_url → notes tab | host/MFE doesn't parse ?tab=notes yet, and the Notes tab is gated behind isStaging | web deep-link (via Notification Center) | ⚠️ open, OQ-17 — two small qontak-customer-fe changes still pending |
No other FE-read field in sections 4/6 was found without a corresponding entry above.
8. Open items
mentioned_users(Decision 15) needs its own grounding pass. §1b's new "Bulk user names lookup" row and §4's response examples are marked 🟡 proposed off the RFC's OQ-13 research (GetUserNamesBulk"already exists" and returns names, but discardsStatus) — this contract could not independently verify the method's exact signature, HTTP path, or response field names againstcontact-service(no sibling checkout consulted for this pass). Confirm before Task 2.5 starts.mentioned_users[].emailis also directly identifying PII (a materially higher classification than the pseudonymousmentioned_user_ids) with no FE consumer wired up yet — it sits under the same infosec approver gate as the rest of this RFC's mention work (RFC Detail 3.D).teams[]/avataron the single-user preview-card detail response — open (OQ-22), non-blocking, confirmed against real code (§4.5/§7): the actualLaunchpadUserDetailResponsestruct has neither field. Ships degraded (name/email/staff level only) unless a client-sidegetTeams()mapping resolves teams.avataron the company user list — now resolved, action needed on the RFC, not the build. §4.4/§7 confirmed via the real FEUserinterface thatavatarnever arrives. Recommend a one-line fix to the RFC's Detail 2.4 endpoint table and Detail 2.G row rather than building FE null-check logic for a field that doesn't exist.- Task-breakdown story-id drift: the task breakdown's Task 2.5 cites story id
NOTE-MENTION-S05-NEG, but the RFC's v1.6 sync renumbered this guard-rail story toS06-NEG(see RFC lines 98-99, Detail 1.B row 20, Detail 1.C). The task breakdown was not regenerated after the renumbering — itsS05-NEGreferences in Task 2.5's title and ACs are stale and should readS06-NEG. Recommend re-runningrfc-task-breakdown(or a manual fix) before relying on that task's story tag. POST /api/v1/notifications/crmrequest shape (§4.6) is unverified against real code —notification-serviceis not checked out locally; the RFC's own claims are grounded against that repo (per its provenance notes), but this contract could not independently confirm them. Re-run the grounding check if/when that repo becomes available as a sibling checkout.- OQ-1 (non-blocking) — reuse the generic
mention(2)notification category vs. add a CDP-specific one; owned by CDP + Notification Platform. - OQ-14 (the RFC's sole true blocker) — Figma frames for the web typeahead picker + chip don't exist yet; gates the visual half of Task 1.1/1.2 only (the anchor/sanitize logic is buildable now).
- OQ-17 (open, small) — web deep-link
?tab=notesparsing + un-gating the Notes tab fromisStaging; tracked as its own chunk in the RFC's Detail 4.D (chunk 10), no task-breakdown id since it's independent FE work. - OQ-19 (infosec ack pending) — auto-FCM fan-out puts the contact name on a mobile lock-screen push with no
skip_fcmopt-out; needs explicit infosec acceptance beforeAGREED, or a generic-descriptionmitigation. Does not block build. - Mobile anchor + backend dual-parse sequencing — per §7's parity check, the mobile mapper change and the backend's dual-form parser (Task 2.2) must ship together; shipping one without the other stores the wrong id type from mobile notes.