RFC Review: Mention User in CDP Notes
Companion review for
rfc-notes-mention-user.md, produced by therfc-reviewerskill. Lives beside the RFC; valid only for the RFC revision inreviewed_rfc_last_updated(2026-06-22).Cycle R3 (delta re-review). The RFC's
last_updatedadvanced2026-06-18 → 2026-06-22(date-based staleness now works — REV-12 fixed). This cycle reviews the 2026-06-22 re-verification, which brought four previously-absent repos into the workspace (notification-service,qontak-unified-component,hub/hub-chat,qontak-launchpad, plusmobile-qontak-chat) and closed the two external notification blockers that held R2 at HOLD.
Executive Summary
- Overall Score:
8.5/10(was8.0at R2 — +0.5) - Rating:
Agentic-Ready - RFC Type:
full-stack - Sub-Type:
frontend:new-feature · backend:new-feature · mobile:enhancement - Assessment Confidence:
High(all delivery layers now in-workspace and code-verified, including the notification service, web unified center, Launchpad, and both mobile apps — the R1/R2 Low-confidence external surface is gone) - Applied Caps/Gates:
ACV lifted 6.5 → 8.5 (outbound Notification contract now VERIFIED against notification-service — the cap that held R2 below Agentic-Ready is released). No category < 5.0. 0 unresolved cross-layer contract mismatches. ROL not capped (BE-first deploy order specified). 8.5 (not higher) because one external design asset (OQ-14 Figma) still gates the single web-composer chunk, plus minor residuals (no FE e2e, no CWV/browser matrix, OQ-17 deep-link wiring). - Implementation Readiness Verdict:
PROCEED — the notification contract is verified (POST /crm; payload pinned), organization_id is dissolved, 0 dangling decisions. 9 of 10 execution chunks are unblocked today; only chunk 7 (web composer) waits on the OQ-14 Figma asset, which is a missing design deliverable, not a spec ambiguity an agent would need to ask about. - Report Path:
cdp/notes-mention-user/rfcs/rfc-notes-mention-user-review.md - RFC Author: CDP Squad | Reviewed: 2026-06-22 (cycle R3)
This R3 delta-review confirms the AUGFLOW-012 re-verification did the one thing R1/R2 could not: it grounded the external notification contract against real code. The dispatch endpoint is corrected to POST /api/v1/notifications/crm (the /chat endpoint rejects non-inbox types — verified notification_handler.go:225,313), the payload is pinned to verified fields (title now correctly required; skip_fcm removed because it does not exist; FCM fan-out is automatic for crm origin), and OQ-8 is dissolved — organization_id is optional *uuid.UUID metadata and company_sso_id is itself a UUID, so no mapping is needed. The web rendering path is now fully grounded (Decision 13): the unified NotificationCenter in qontak-unified-component is hosted in hub/hub-chat, already renders the mention category, and qontak-customer-fe embeds via module federation. Mobile scope is resolved (OQ-18): mobile-qontak-crm only; mobile-qontak-chat has no CDP notes and is excluded. The remaining gate is OQ-14 (web Figma) — external and Design-owned — which blocks only the web-composer chunk. Net: the feature moves from HOLD to PROCEED, with one design-asset carve-out on a single chunk.
Quick Verdict
Why this RFC can be implemented agentically:
- The outbound notification contract — the single drag that held ACV at 6.5 across R1/R2 — is verified, not assumed. §2.4 + Decision 10 now pin endpoint (
/crm), auth (X-Api-Key), required fields (sso_id/title/description), taxonomy (notif_type="1",notif_category="2"), and the optionalorganization_id. An agent can write chunk 5's request body without guessing. organization_id(OQ-8) is no longer a question: optional metadata,company_sso_idis a UUID. Decision 10's interface is complete.- 0 dangling decisions; Decision 12 has a default (Tiptap fallback) and Tiptap is now inventoried with a bundle budget (REV-11 fixed). Decision 13 grounds the web/mobile delivery surfaces.
Why this RFC will still cause agent guessing or rework:
- Chunk 7 (web composer) is still gated on Figma (OQ-14 / REV-4). No design exists for the typeahead picker + chip; an agent cannot build pixel-correct UI from nothing. This is a missing deliverable, not an answerable clarification.
- Web deep-link wiring (OQ-17 / REV-13, new): the unified-center click lands on
/customers/{id}?tab=notes, butCustomerActivityV2.vuedoes not parse?tab=notesand the Notes tab is gated behindisStaging. Without chunk 10, the web tap-through resolves to the customer page but not the Notes tab. Small, internal, scoped.
Findings Ledger (carry-forward)
Stable, never-renumbered ids. R1 minted REV-1…REV-10; R2 added REV-11/REV-12; R3 reconciles all and mints REV-13/REV-14. "Was this captured before?" = the id exists; "did we improve?" =
open → fixedbelow.
| ID | Severity | Finding (one line) | RFC location | Status | First seen | Resolved in | Evidence / fix |
|---|---|---|---|---|---|---|---|
REV-1 | blocker | Notification Service contract UNVERIFIED — notif_type/notif_category/event_type + web parity not confirmed; dispatch payload cannot be finalized | §2.4 outbound dep call; Decision 10; OQ-1 | fixed | R1 | R3 | notification-service now in workspace. Verified: endpoint POST /api/v1/notifications/crm (route.go:57-58; /chat rejects non-type-3, notification_handler.go:225,313); payload sso_id/title/description required, notif_type="1"/notif_category="2" (notification_handler.go:67-83, notification_attributes.go:122-141); web center renders mention (typeAndCategory.ts:40-43). OQ-1 narrowed to a non-blocking product choice (reuse vs new category). |
REV-2 | blocker | organization_id mapping unresolved — company_sso_id (string) → company UUID undefined | §1 Assumptions #4; §2.4; OQ-8 | fixed (dissolved) | R1 | R3 | Verified organization_id is optional *uuid.UUID metadata, not the recipient (notification_handler.go:67-83); company_sso_id is itself a UUID (Launchpad models.go:15-27). No mapping or extra lookup needed. OQ-8 resolved. |
REV-3 | major→minor | Web editor mention-trigger feasibility — Decision 12 was dangling | Decision 12; §4.D ch.7; OQ-7 | fixed (decision) / spike-half open | R1 | R2 | Decision 12 Partial with pre-committed Tiptap fallback; the spike (pixel3-native vs Tiptap) is an optimization, not a blocker. Unchanged at R3. |
REV-4 | blocker | Web Figma frames for typeahead picker + chip do not exist | §1 Design References; OQ-14 | open | R1 | — | OQ-14 still Open; Design-owned, external to code repos. Gates only §4.D ch.7. Now the sole remaining blocker. |
REV-5 | major | Company-active-user validation method on IUserService unverified | §2.0 contracts; §4.D ch.4a; OQ-13 | fixed | R1 | R2 | GetActiveUsersBySsoIds wrapper spec'd; chunk 4a. R3 note: Launchpad now in workspace confirms the underlying GET /private/users?statuses[]=active&sso_ids[]=&company_sso_id= primitive (user_handler.go:459). |
REV-6 | major | OQ-9 (private-note + mention) left TBD | §3.A.1; §3.D; OQ-9 | fixed | R1 | R2 | No privacy model exists; notify normally. Unchanged. |
REV-7 | major | Idempotency key included note_updated_at → dedup broke under concurrent PUTs | §2.D; §2.E; §2.F | fixed | R1 | R2 | Key note_id + recipient_sso_id, timestamp-free + mandatory. Unchanged. |
REV-8 | minor | Idempotency key "recommended" not mandated; DLQ retention unstated | §2.D; §2.F | fixed | R1 | R2 | Mandated; retry/DLQ grounded to worker_service.go/backoff.go. Unchanged. |
REV-9 | minor | Web FE on deprecated note path; new fields must appear on both route groups | §2.4 note; OQ-15 | fixed | R1 | R2 | Both groups share one handler. Unchanged. |
REV-10 | minor | mention_count semantics unpinned; FE i18n keys TBD | §3 Monitoring; §3.C; OQ-16 | fixed | R1 | R2 | mention_count = newly-added. Unchanged. |
REV-11 | minor | Tiptap fallback dependency not inventoried; no bundle budget | Decision 12; §1 Dependencies; §2.J; §3 Perf | fixed | R2 | R3 | Verified present: @tiptap/vue-3 + @tiptap/extension-mention row in §1 Dependencies (l.205); bundle budget (~50–80KB gzipped, lazy-load) in §3 Performance. |
REV-12 | minor | last_updated not bumped despite material commits — staleness undetectable by date | Frontmatter / Metadata table | fixed | R2 | R3 | last_updated bumped to 2026-06-22; Metadata row annotated with the re-verification. Date-based staleness now works. |
REV-13 | minor | Web deep-link does not land on the Notes tab — ?tab=notes not parsed + Notes tab gated behind isStaging | Decision 13; §2.G; OQ-17; §4.D ch.10 | open | R3 | — | New. Two small qontak-customer-fe changes (parse query param; un-gate tab). No change to qontak-unified-component or hosts. RFC documents it (OQ-17) and gives it chunk 10. Internal, scoped, non-blocking for BE/mobile. |
REV-14 | low | Auto-FCM means contact name (description="On contact {Name}") surfaces on a mobile lock-screen push, no skip_fcm opt-out | §3.D; Decision 9/10 | open (pending infosec ack) | R3 | — | New. RFC §3.D now calls this out explicitly and offers a one-line mitigation (generic description). Legal basis unchanged (recipient is an authorized same-company viewer). Needs explicit infosec acceptance before AGREED — folds into the existing infosec gate, does not block build. |
Ledger summary: 3 open (1 blocker REV-4 external; 1 minor REV-13 internal; 1 low REV-14 infosec-ack), 4 transitions to fixed this cycle (REV-1, REV-2, REV-11, REV-12). Counting by id across all cycles: of REV-1…REV-14 → 11 fixed (REV-1/2/3-decision/5/6/7/8/9/10/11/12), 3 open (REV-4 external blocker, REV-13 minor internal, REV-14 low). The only true blocker left is external/Design (REV-4 / OQ-14). Still-open findings are promoted to the RFC §5 Open-Questions table (OQ-14 carried; OQ-17 added this cycle; REV-14 captured in §3.D).
PRD → RFC Traceability Matrix
Forward — PRD element → RFC coverage (R3 deltas marked):
| PRD Element | RFC Section | Coverage |
|---|---|---|
| S01 Mention teammate (Web) — AC-1/2/3, ERR-1 | §1.C, §2.A, §2.B, §2.4, §2.3, §4.D ch.4b/6/7/10 | Full (ch.7 UI still Figma-gated) |
| S02 Mention teammate (Mobile) — AC-1/2/3, ERR-1 | §1.C, Decision 8, §2.4 dual-parse, §4.D ch.5/8 | Full |
| S03 Mentioned user notified — AC-1/2/2b/3, ERR-1/2 | §2.2, §2.F, §3.A, Decision 4/9/10/13, §4.D ch.5/9 | Full (upgraded from Partial — the web Notification Center path and dispatch payload are now verified, OQ-1 closed) |
| S04 Only valid company users mentionable — AC-1, ERR-1/2 | §2.4 validation, Decision 5, §2.0 contract, §4.D ch.4a/4b | Full |
| S05-NEG cross-company / non-user guard — NEG-1/2 | §4 flag gate, §2.4 company-scope | Full |
| PRD §6 Constraints (10k limit, SSO identity, server sanitize, DOMPurify, async, latency) | §2.3, §3.B, Decisions 1/3/6, §2.A, Decision 2/§2.F, §3 Perf | Full |
| PRD §6 Constraint: plan gating Growth+Enterprise | §1 Assumptions #5 | Partial — assumption only; flag-gated (acceptable) |
| PRD §8 #1 typeahead list | §2.4, §2.B, Decision 11 | Full (Launchpad ?query= search now verified, OQ-6 closed) |
| PRD §8 #3 dispatch payload | §2.4 dependency-call table, Decision 10 | Full (upgraded from Partial — payload verified against notification-service) |
| PRD §8 #4 mobile tap-through | Decision 9, §2.F.1 step 6 | Full |
| PRD §10 Rollout (3 stages) | §4 Rollout Strategy | Full |
| PRD §11 Observability (5 events) | §3 Monitoring | Full |
| PRD §12 Success Metrics | §1 Success Criteria | Full |
| PRD §13 Dependencies | §1 Dependencies, §2.F.1 | Full (Tiptap inventoried — REV-11 fixed) |
| PRD §14 Decisions D-1…D-10 | §2 Decisions 1–13 + §1.B mapping | Full |
| PRD §15 OQ-1…OQ-12 | §5 (carried + OQ-13…OQ-18) | Full |
Reverse — RFC artifact → PRD driver: unchanged; every §1.B/§2.I artifact traces to a PRD AC. The new artifacts this cycle (Decision 13 web/mobile scoping; chunk 10 deep-link wiring) trace to S03/AC-2/AC-2b. No scope creep.
Summary: 18 of 20 PRD elements Full (S03 + dispatch payload upgraded Partial→Full; §13 dependency gap closed), 2 Partial (plan-gating = flag-gated, acceptable; ch.7 UI Figma-gated under S01), 0 Missing. Bidirectional traceability remains exemplary.
Scorecard
Full-Stack Scorecard (18 categories)
| # | Category | Source | Score | Evidence-Based Rationale (Δ vs R2) |
|---|---|---|---|---|
| 1 | PRT — PRD Traceability | Merged | 9.0 | Unchanged. Forward+reverse matrices, composite AC ids, D-n→Decision-n map. S03 + payload upgraded Partial→Full; §13 gap closed (REV-11). |
| 2 | TDC — Technical Decisions | Merged | 8.5 | +0.5. Decision 10 moved Partial→Resolved (payload verified, OQ-1/OQ-8 closed); Decision 13 added (web unified-center + MFE + mobile scoping, fully grounded). 13 ADR-format decisions; 0 dangling, 0 blocking-partial (Decision 12 spike-half is an optional optimization). |
| 3 | CNT — Contract Specificity | FE | 7.5 | Unchanged. mention_count/i18n pinned; Tiptap inventoried. Still minus: picker props pending Figma (OQ-14) — the one FE contract not pinnable from code. |
| 4 | SCB — Scope Boundaries | FE | 8.5 | Unchanged. §2.I per-layer create/modify/NOT-touch; shared-module isolation; Decision 13 adds explicit mobile-qontak-chat exclusion + "no change to unified-component/hosts." |
| 5 | DEP — Dependencies | FE | 8.0 | +0.5. REV-11 fixed: Tiptap (@tiptap/*) now in §1 Dependency table + §2.J + §3 bundle budget. New downstream deps (notification-service, qontak-unified-component, Launchpad) all verified and tabled. |
| 6 | NFS — Non-Functional Specificity | FE | 7.5 | +0.5. Bundle budget now present (~50–80KB gzipped, lazy-load, asserted in npm run build — REV-11). Still no CWV (LCP/INP/CLS) or browser matrix. |
| 7 | TPS — Test Plan Specificity | FE | 7.5 | Unchanged. §4.C repo-sourced commands; §4.D chunks carry assertable AC (incl. new ch.10). Still no FE e2e (follow-up). |
| 8 | DMS — Data Model & Schema | BE | 8.5 | Unchanged. Struct+bson delta, example doc, cardinality, PII, retention, optional index + .down.json. |
| 9 | ACV — API Contract & Versioning | BE | 8.5 | +2.0 — the headline. The outbound Notification contract, UNVERIFIED across R1/R2, is now verified against notification-service: endpoint /crm, auth, required/optional fields, taxonomy, organization_id semantics (§2.4, Decision 10; anchors route.go:57-58, notification_handler.go:67-83/225/313). Internal endpoints already complete; the persistent ACV drag is released. |
| 10 | DIC — Data Integrity & Consistency | BE | 8.5 | Unchanged. Timestamp-free mandatory idempotency key (REV-7); atomic single-doc writes; diff-notify. |
| 11 | FMC — Failure Mode Coverage | Merged | 8.5 | Unchanged. FE UI-state matrix + error catalogs; BE timeout/retry×3/DLQ/poison; cross-layer error shapes match. R3 adds the auto-FCM delivery path (no new failure mode — fan-out is platform-owned). |
| 12 | CSS — Concurrency & Scaling | BE | 7.5 | Unchanged. Collision map; timestamp-free key dedups across concurrent PUTs. Last-write-wins on body is an inherited limitation; no endpoint rate limit named. |
| 13 | SAS — Security & Authorization | BE | 8.5 | Unchanged. bluemonday allow-list w/ href-scheme rejection; UUID validation; company-scoped tenancy (verified); SSRF note (server-constructed click_action_url); Vault secrets; gosec. R3 adds §2.G rows confirming only server-constructed strings reach the notification body and the deep-link host is trusted. |
| 14 | ROL — Rollout & Rollback | Merged | 8.5 | Unchanged. BE-first order + cross-layer matrix; single logical flag; quantified stop conditions; deploy-order-aware rollback; additive field. |
| 15 | OBS — Observability | Merged | 8.5 | Unchanged. 5 named events; mention_count pinned; OTel/Datadog into worker; SLO 99%; DLQ surfaced as metric. Minus: no trace into the external service (out of CDP control). |
| 16 | SBC — Service Boundary & Coupling | BE | 8.5 | Unchanged. §2.F.1 responsibility matrix; new NotificationClient mirrors verified heimdall pattern; async via IJobEnqueuer. Decision 13 cleanly bounds web rendering to the platform component (no CDP-local UI). |
| 17 | CPA — Pattern Alignment | Merged | 8.5 | Unchanged. §2.0 Patterns cite exact in-repo files; MFE embed + unified-center import grounded to real anchors (TheNavbar.vue:79, hub-chat/nuxt.config.ts:526). |
| 18 | CDG — Compliance & Data Governance | BE | 8.0 | +0.5. §3.D now fully characterizes the egress, including the newly-verified auto-FCM lock-screen exposure of the contact name (REV-14) with a concrete mitigation; legal basis intact (recipient is an authorized same-company viewer). Infosec gate explicit. Residual: egressed body not recallable (D-6, accepted). |
Resource & Cost Advisory (non-blocking)
- §4.F: negligible compute/DB delta; ≤10 external calls/note + one batched company-user lookup; no new infra. Bundle delta for the Tiptap fallback is now budgeted (REV-11). Advisory only — no score/verdict impact.
Decision Closure Assessment
Decision Index
| # | Decision | Status | Critical Gaps |
|---|---|---|---|
| 1 | Storage as mentioned_user_ids []string on Mongo doc | Resolved | none |
| 2 | Async dispatch via gocraft/work worker | Resolved | none |
| 3 | Server-side bluemonday sanitization | Resolved | needs infosec sign-off (gate) |
| 4 | Notify only newly-added mentions (diff) | Resolved | none |
| 5 | Invalid mention → drop-and-warn | Resolved | none |
| 6 | Max 10 mentions/note | Resolved | tunable const |
| 7 | Mobile plain-text fallback | Resolved | none |
| 8 | Mobile emits sso_id; BE dual-parses | Resolved | none |
| 9 | Mobile tap-through via external_url (mobile-qontak-crm only) | Resolved | confirm host/URL w/ Mobile (operational) |
| 10 | Reuse notification-service via new client — POST /crm | Resolved (was Partial) | payload verified; OQ-1 narrowed to a non-blocking product choice (reuse vs new category) |
| 11 | Typeahead = existing list, client-filtered | Resolved | Launchpad ?query= now verified available (OQ-6) |
| 12 | Web @-trigger in MpRichTextEditor | Partial | fallback (Tiptap) chosen + inventoried; spike only picks A vs C; Figma (OQ-14) gates the chunk |
| 13 | Web rendering via unified center + MFE; mobile scoped to mobile-qontak-crm | Resolved (new) | OQ-17 deep-link wiring (small, chunk 10) |
Aggregate: 12 of 13 Resolved, 1 Partial (Decision 12 — editor-path optimization, not a blocker). R2 had 10 Resolved / 2 Partial / 0 Dangling. Decision 10 promoted Partial→Resolved; Decision 13 added Resolved. 0 dangling, 0 blocking-partial decisions remain.
Decision: 10 — Reuse notification-service via a new outbound client (POST /crm)
Status: Resolved (was Partial at R1/R2 — the headline R3 improvement)
What was decided
Option B — new NotificationClient mirroring QontakLaunchpadClient (heimdall), targeting POST /api/v1/notifications/crm with X-Api-Key. Transport/auth/timeout/logging decided and grounded (qontak_launchpad.go:38).
Interface specification — now complete
§2.4 + Decision 10 pin every field against verified code: sso_id (required, handler-enforced notification_handler.go:296-301), title/description (required), notif_type="1", notif_category="2", click_action/click_action_url, organization_id (optional *uuid.UUID). The R1/R2 TBD tags are gone. skip_fcm correctly removed (does not exist; FCM fan-out is automatic for crm origin per service.go:47-49).
Failure handling
Fully specified (§2.F): 10s timeout, retry×3 (1,4,16,64s), log on final failure, note unaffected, mandatory timestamp-free idempotency key.
Challenge results
- Scale: ≤10 calls/note, async — holds at 10x.
- Reversibility: remove client; job no-ops behind flag. Low cost.
- Consistency: consistent with Decisions 2/9/13; the verified auto-FCM path means Decision 9's mobile delivery is reached without a flag.
- Agent implementability: now yes for the full body — an agent can construct the
/crmrequest from the RFC alone.
Gaps and suggestions
Missing: none blocking. Product choice (non-blocking): reuse the generic mention(2) category or add a CDP-specific category in notification-service for analytics/filtering (OQ-1, narrowed).
Open questions: OQ-1 (narrowed, non-blocking).
Decision: 13 — Web rendering via unified Notification Center + MFE; mobile scoped to mobile-qontak-crm
Status: Resolved (new this cycle)
What was decided
Web mention notifications render in the unified NotificationCenter (qontak-unified-component) hosted in hub/hub-chat; qontak-customer-fe embeds via module federation (RemoteContactRouter) at /customers/**; mobile delivery is mobile-qontak-crm only (mobile-qontak-chat excluded).
Grounding in existing code
NotificationCenter.vue + mention category (typeAndCategory.ts:40-43); import hub-chat/.../TheNavbar.vue:79; CRM-origin click routing useNotificationCenter.ts:35-44; MFE expose qontak-customer-fe/nuxt.config.ts:92-111 + consume hub-chat/nuxt.config.ts:526, pages/customers/[...all].vue:69; mobile ownership crm_note/.../note_screen.dart, mention_toolbar_botton.dart; mobile-qontak-chat exclusion app.dart:85-113, contact_detail_screen.dart.
Interface specification
No change to qontak-unified-component or the hosts. The CDP-side contract is the notification payload (Decision 10) + the click_action_url deep-link.
Failure handling / challenge results
- Agent implementability: yes for the BE emit + mobile delivery; the web tap-through needs chunk 10 (OQ-17) to land on the Notes tab.
- Reversibility: additive; only the deep-link precision degrades if OQ-17 is left open.
Gaps and suggestions
Missing: OQ-17 wiring — parse ?tab=notes, un-gate the Notes tab in CustomerActivityV2.vue (chunk 10, small).
Open questions: OQ-17 (REV-13).
Decisions 1–9, 11 remain Resolved (abbreviated per the ≥7.0 optimization). Decision 12 unchanged (Partial; editor-path optimization, Figma gates the chunk).
UI State Audit
| Component | Loading | Empty | Error | Partial | Success | Assessment |
|---|---|---|---|---|---|---|
| Web typeahead | defined | defined | defined | n/a | defined | 4/4 |
| Web mention chip (render) | n/a | defined | defined (warn chip) | defined (mix) | defined | 4/4 |
| Mobile picker | defined | defined | defined | n/a | defined | 4/4 |
| Mobile note render | n/a | defined | defined (never raw HTML) | n/a | defined | 3/3 |
Summary: All surfaces fully defined (§2.C). Unchanged from R2.
Performance Budget Check
| Metric | Target | Source | Assessment |
|---|---|---|---|
| Typeahead P95 | ≤500ms | §3 | adequate (escalation trigger defined) |
| Note write P95 | ≤2s | PRD §6 | adequate |
| Bundle size delta (Tiptap fallback) | ~50–80KB gzipped, lazy-load | §3 (REV-11) | now specified (was missing at R1/R2) |
| LCP / INP / CLS | — | — | still missing (minor) |
REV-11 closes the bundle gap. CWV still absent — minor, not a blocker.
Accessibility Review
| Aspect | Specified? | Assessment |
|---|---|---|
| Keyboard navigation | yes (arrow/enter/escape, §3.E) | adequate |
| Focus management | partial (aria-activedescendant; return-focus on picker close not described) | incomplete (minor) |
| ARIA labels | yes (role=listbox/option; chip role=link) | adequate |
| Color contrast | yes (≥4.5:1 token-driven) | adequate |
| Motion sensitivity | no | minor |
| Screen reader | partial (listbox semantics; chip announces @Name) | adequate |
Unchanged from R1/R2.
Cross-Layer Contract Verification
| Endpoint / Contract | BE / source | FE / Mobile expected | Match? | Gaps |
|---|---|---|---|---|
POST/PUT …/notes | new fields, both route groups via shared handler | FE maps snake→camel | Yes | closing chunk §4.D ch.6 |
| Company user list | [{ sso_id, full_name?, status }] (Launchpad — avatar absent) | picker needs sso_id + null-safe name + initials fallback | Yes | avatar-absence handled (Decision 11 note) |
| Mention anchor (web/mobile) | data-user-id / href sso_id | composer/mapper emit; DOMPurify ADD_ATTR | Partial | closing chunks ch.6/8 |
Notification body (title/description) | server-constructed strings only (never note HTML) | unified center + One Notif V2 render as text | Yes (low risk) | note HTML sanitized server-side; not placed in body |
Web deep-link click_action_url | /customers/{id}?tab=notes (trusted host) | unified center → MFE → notes tab | Partial (OQ-17) | host/MFE must parse ?tab=notes + un-gate tab (ch.10) |
| Notification dispatch (outbound) | POST /crm verified payload | n/a (platform) | Yes (was the R1/R2 gap) | OQ-1 product choice only |
Mismatches found: 0 unresolved. The outbound-contract gap that was the standing ACV drag is closed; two new rows (notification body, deep-link) are added — the deep-link is the one Partial, with chunk 10 as its closer.
Cross-Layer Rollout Compatibility Matrix
| Scenario | FE | BE | Works? | Notes |
|---|---|---|---|---|
| Pre-deploy | Old | Old | Yes | baseline |
| Backend first | Old | New (flag OFF/dark) | Yes | BE ignores anchors; flag OFF → no dispatch |
| Frontend first | New | Old | No | avoided by chosen BE-first order |
| Both deployed | New | New | Yes | target |
| Backend rollback | New | Old | No | roll back FE first or flag OFF; additive field data-safe |
| Frontend rollback | Old | New | Yes | BE still parses stored anchors; chips degrade |
Deploy order: Backend first. ROL not capped. Unchanged.
End-to-End Data Flow
Flow: Web — create note with a new mention (R3, grounded)
Agent picks @user (FE: UserStore.getUsers, client-filtered; Launchpad ?query= available)
→ NoteInput.vue inserts <a data-user-id data-mention>; DOMPurify ADD_ATTR keeps it
→ POST/PUT /iag/v1/contacts/{id}/notes [both route groups → same handler, REV-9]
→ ContactNotesService: mention.Parser → IUserService.GetActiveUsersBySsoIds (company-scoped, active)
→ bluemonday sanitize → drop invalid/self
→ repo insert/$set MongoDB (note + mentioned_user_ids)
→ IJobEnqueuer.EnqueueJob per new mention; analytics cdp_note_mention_added (newly-added)
→ 200 { mentioned_user_ids, dropped_mentions? }
→ FE CustomerStore reads new fields → NotesList renders chip
→ (async) worker → POST /api/v1/notifications/crm [VERIFIED payload: sso_id,title,description,notif_type=1,notif_category=2,click_action_url]
key note_id+recipient_sso_id (REV-7)
→ web: unified NotificationCenter (hub/hub-chat) renders mention → click → click_action_url → MFE RemoteContactRouter → /customers/{id}?tab=notes [needs OQ-17 ch.10 to select Notes tab]
→ mobile: auto-FCM (crm origin) → mobile-qontak-crm One Notif V2 → external_url tap-through
Gaps in flow: the worker→notification step is now fully specified (was the R1/R2 TBD). The only residual is the web deep-link landing precisely on the Notes tab (OQ-17 / chunk 10). Every other hop is grounded.
Strengths
- The external contract is grounded, not assumed (ACV 6.5→8.5). The AUGFLOW-012 re-verification opened
notification-serviceand pinned the dispatch endpoint and payload against real code — including the non-obvious correction that a CDP mention must use/crm, not/chat(the latter rejects non-inbox types,notification_handler.go:225,313). This is the single change that lifts the feature to Agentic-Ready. - Two standing blockers dissolved by verification, not by assertion. OQ-8 (
organization_id) was answered by reading both the notification request struct (optional*uuid.UUID) and the Launchpad company model (sso_idis a UUID) — so no mapping exists to build. OQ-1's taxonomy is confirmed and the web center already rendersmention. - The whole delivery topology is now first-party verified. Decision 13 grounds the web unified-center + MFE embed and scopes mobile to the one app that can display a CDP note (
mobile-qontak-crm), explicitly excludingmobile-qontak-chat. No "external/UNVERIFIED" hand-waving remains in the notification path.
Biggest Gaps
- Web Figma frames still pending (OQ-14 → REV-4, §1 Design References). No design for the typeahead picker + chip; gates §4.D ch.7. External/Design-owned — the sole remaining blocker, and it blocks only one chunk.
- Web deep-link does not yet land on the Notes tab (OQ-17 → REV-13, Decision 13 / §2.G).
?tab=notesis not parsed and the Notes tab is gated behindisStaging; without chunk 10 the unified-center click reaches the customer page but not the notes view. Small, internal, scoped. - Auto-FCM PII on lock-screen (REV-14, §3.D). Contact name surfaces on a mobile push with no
skip_fcmopt-out. Documented with a one-line mitigation; needs explicit infosec acceptance (folds into the existing gate). Low.
Priority Actions
- OQ-14 (§1 Design References / §4.D ch.7) — Create the two Figma frames (picker + chip). Design-owned; the only remaining blocker. Unblocks the web composer chunk. (Independent of the now-resolved editor decision.)
- OQ-17 / REV-13 (§4.D ch.10) — Wire the web deep-link: parse
?tab=notesinCustomerActivityV2.vueand un-gate the Notes tab fromisStaging. Smallqontak-customer-fechange; lands the web tap-through. - REV-14 (§3.D / infosec gate) — Get explicit infosec acceptance that the contact name may appear on a mobile lock-screen push (or adopt the generic-
descriptionmitigation). Required beforeAGREED, not before build. - OQ-1 (non-blocking product choice) — Decide with Notification/Platform whether to reuse the generic
mention(2)category or add a CDP-specific one for analytics/filtering. Reuse is sufficient for v1.
Implementation Readiness Checklist
Unblocked (agent can proceed)
All types: PRD↔RFC traceability ✓; 0 dangling decisions ✓; failure modes + error catalogs ✓; config contract ✓; pattern alignment ✓; rollout + rollback ✓; observability ✓; task decomposition with AC (10 chunks) ✓; vague words near-zero ✓.
Frontend: UI states ✓; a11y specified (focus-return minor) ✓; bundle budget now present ✓. Pending: picker prop types (Figma OQ-14); CWV/browser matrix (minor).
Full-stack: cross-layer contract verified (partials have closing chunks) ✓; BE-first deploy order ✓; rollout matrix ✓; e2e flow ✓; flag coordination ✓.
Backend: schema at struct/bson precision ✓; internal + outbound contracts now complete ✓; idempotency key fixed ✓; concurrency map ✓; security/tenancy ✓; additive migration ✓; service boundary ✓; compliance (infosec gate set; REV-14 to ack) ✓.
Blocked (must fix first)
- OQ-14 — web Figma frames (REV-4, Design-owned) — gates only chunk 7
Internal follow-ups (do not block BE/mobile)
- OQ-17 — web deep-link to Notes tab (REV-13) — chunk 10
- REV-14 — infosec ack of lock-screen contact name (before AGREED)
Verdict: PROCEED — 9 of 10 chunks unblocked (BE 1–3, 4a, 4b, 5 full, FE ch.6, Mobile 8–9, + ch.10 deep-link); only ch.7 (web composer) waits on the OQ-14 Figma asset.
Task Manifest
| Order | Chunk | Files | Acceptance Criteria | Dependencies |
|---|---|---|---|---|
| 1 | BE field + optional index | repository/contact_notes/base.go; db/migrations/0NN_*.json | struct compiles; bson round-trips; index applies+rolls back | None — unblocked |
| 2 | mention.Parser (dual-form) | service/mention/parser.go(+_test) | both forms → same []sso_id; malformed dropped; >10 errors | Chunk 1 — unblocked |
| 3 | bluemonday policy | service/mention/policy.go; go.mod | golden test: allow-list survives, scripts/javascript:/data:/style stripped | None — unblocked |
| 4a | IUserService.GetActiveUsersBySsoIds | service/launchpad/user_service.go (+interface) | given sso_ids+company → active members only | None — unblocked |
| 4b | Wire parse+validate+sanitize+persist; drop-and-warn; diff | contact_notes_service.go; payload/* | valid stored + dropped_mentions; diff notifies new; self filtered | Chunks 2,3,4a — unblocked |
| 5 | NotificationClient (POST /crm, payload finalized) + MentionNotifyJob | api/notification_client.go; service/.../mention_notify_job.go | request sends verified fields w/ X-Api-Key; enqueue per new recipient; 5xx→retry×3→log; key dedups | Chunk 4b — fully unblocked (was payload-blocked) |
| 6 | FE DOMPurify ADD_ATTR + read new fields | NoteInput.vue:460; NotesList.vue:104; CustomerStore.ts | data-* survive sanitize; warn chip shows | Chunk 1 — unblocked |
| 7 | FE typeahead + chip (pixel3-native or Tiptap fallback) | MentionPicker/MpAutocomplete or Tiptap; NoteInput.vue; chip CSS | lists users; insert anchor; chip renders | blocked on OQ-14 (Figma) |
| 8 | Mobile CDP mapper emits sso_id | mention_toolbar_botton.dart l.398-402 | href = .../users/{ssoId}/edit_user; list shows @Name | Chunk 2 — unblocked |
| 9 | Mobile external_url route verify (mobile-qontak-crm only) | notification_item_v2_mixin.dart, bottom_navigation_screen_mixin.dart:253-308 (verify-only) | origin=external_url opens click_action_url; mobile-qontak-chat not targeted | unblocked |
| 10 | FE web deep-link to Notes tab (OQ-17) | qontak-customer-fe: CustomerActivityV2.vue parse ?tab=notes; un-gate tab | /customers/{id}?tab=notes auto-selects Notes tab | unblocked (internal) |
Dangling Decisions Log
| # | Decision | Location | Owner | Deadline |
|---|---|---|---|---|
| — | None. Decision 10 moved Partial→Resolved (payload verified); Decision 13 added Resolved; Decision 12 remains Partial on an editor-path optimization (not dangling). | — | — | — |
Open Questions
| # | Question | Category | Severity |
|---|---|---|---|
| 1 | Web Figma frames for picker + chip (REV-4 / OQ-14) | CNT/NFS | Blocking (external/Design, 1 chunk) |
| 2 | Web deep-link: parse ?tab=notes + un-gate Notes tab (REV-13 / OQ-17) | TDC/CNT | Important (internal, small) |
| 3 | Infosec ack: contact name on mobile lock-screen push, no opt-out (REV-14) | CDG | Important (before AGREED) |
| 4 | Reuse mention(2) category vs add CDP-specific category (OQ-1, narrowed) | ACV/product | Nice-to-have (non-blocking) |
Evidence Notes
- §2.4 + Decision 10 + OQ-1/OQ-8 — re-verified against in-workspace
notification-service:/crmendpoint, required/optional fields, taxonomy, optionalorganization_id. Closed REV-1; dissolved REV-2 → ACV 6.5→8.5, TDC +0.5, S03/payload Partial→Full. - Decision 13 + §2.G — web unified-center + MFE + mobile scoping grounded to
qontak-unified-component/hub/hub-chat/qontak-customer-fe/mobile-qontak-*. New Resolved decision; surfaced REV-13 (deep-link wiring) and REV-14 (lock-screen PII). - §1 Dependencies l.205 + §3 Performance — Tiptap (
@tiptap/*) inventoried with bundle budget → closed REV-11, DEP +0.5, NFS +0.5. - Frontmatter
last_updated: 2026-06-22— bumped; date-based staleness now works → closed REV-12. - §3.D — auto-FCM lock-screen egress characterized with mitigation → CDG +0.5; REV-14 logged for infosec ack.
- Launchpad
user_handler.go:459—GET /private/users?query=&statuses[]=activeconfirms OQ-6;avatarabsent from list response (noted in Decision 11).
Review History
| Cycle | Date | Reviewed RFC revision (last_updated / commit) | Score | Verdict | Findings open → fixed | Notes |
|---|---|---|---|---|---|---|
R1 | 2026-06-18 | 2026-06-18 / 2d43cb2 | 7.5 | HOLD | 10 open (4B/4M/2m), 0 fixed | Baseline. External-dep blockers + one internal correctness fix (REV-7 key). |
R2 | 2026-06-18 | 2026-06-18 (unchanged) / 5883ba8 | 8.0 | HOLD | 7 fixed (REV-3 decision-half/5/6/7/8/9/10), 4 open (REV-1/2/4 external + REV-11 new), 2 new minor (REV-11/12) | All internal findings closed with grounded verifications; 0 dangling decisions. Held <8.5 by the unverified external Notification contract (ACV 6.5). |
R3 | 2026-06-22 | 2026-06-22 / working tree (base 5f1d4c6) | 8.5 | PROCEED | 4 fixed (REV-1/2/11/12), 3 open (REV-4 external blocker; REV-13 minor internal; REV-14 low), 2 new (REV-13/14) | AUGFLOW-012 re-verification brought notification-service/qontak-unified-component/hub/hub-chat/qontak-launchpad/mobile-qontak-chat into the workspace. Notification contract verified (/crm, payload pinned); OQ-8 dissolved; OQ-6 closed; Decision 10 Partial→Resolved; Decision 13 added; mobile scoped to mobile-qontak-crm. ACV 6.5→8.5 releases the cap. Only OQ-14 (Figma) blocks, and only chunk 7. Score +0.5; rating Strong→Agentic-Ready; verdict HOLD→PROCEED. |