Skip to main content

RFC: Centralized Web Session — CRM Frontend Integration of @mekari/sdk Session

Document Conventions (do not remove)

Follows the Qontak RFC Template governance shell (metadata table + sections 1–6 + Comment logs) and is agent-execution-ready (§2 Repo Reading Guide, mermaid diagrams, §4 Execution Plan + Verification & Rollback Recipe). YAML frontmatter is the machine-readable index; the metadata table is the human record. Both agree on every shared field.

Scope guard. This RFC covers the CRM frontend repository only (/Users/mekari/Documents/crm). The Mekari Session service (Golang), the dedicated Redis, the Kong/MAG gateway path account.mekari.com/sm/*, and the SSO session store changes are owned by Account & Launchpad and live in other repos — they are READ-ONLY context here, captured to anchor the CRM integration. No backend code is specified in this RFC.


Metadata

FieldValueNotes
StatusRFCOpen for review; not yet Ready for agent execution (see §7)
Type / Sub-typefrontend / enhancementCRM consumes a new shared SDK; no new product surface beyond a notification
TitleCentralized Web Session — CRM FE Integration of @mekari/sdk Session
OwnerAccount & Launchpad (SDK) · CRM Frontend (this integration)Cross-squad
AuthorsQontak CRM Frontend
ReviewersA&L (SSO/SM), CRM FE tech leadCross-squad review required
Approver(s)CRM FE tech leader, Infosec [REQUIRED]Infosec gates CSP / SDK origin policy
Submitted2026-06-27ISO-8601
Last updated2026-06-27ISO-8601
Target release2026-Q3Pilot is Launchpad first (PRD §4.4); CRM follows in step 5
Related documentsCentralized Web Session RFC (Confluence)Driver
Discussion[REQUIRED]Slack thread

Sections at a Glance

§SectionCRM-FE hint
1OverviewProblem, success criteria, scope, Detail 1.A coverage matrices, 1.B decision index, 1.C per-story change map
2Technical DesignDetail 2.0 Repo Reading Guide (Repo Map, anchors, source verification, patterns, reading order), infra topology, ADR blocks, no-DDL, API matrices, sequence + state + branch diagrams
3High-Availability & SecuritySDK origin/CSP policy, FE observability, msli fallback integrity
4Backwards Compatibility & RolloutFeature toggle centralized_session, Agent Execution Plan, Verification & Rollback Recipe
5Concerns, Questions, Known LimitationsSeverity-tagged open questions
6Comment logsReview trail
7Ready for agent executionMarker + failing gates

1. Overview

1.1 Context & Problem

Mekari products each maintain their own session and do not react when the user logs out, switches account, or goes idle on SSO. CRM is one such product: its session lives in qcrm_access_token / qcrm_refresh_token cookies and an @nuxtjs/auth Vuex strategy (schemes/crmAuthScheme.js), refreshed on a local timer (utils/helpers/auth.js:setupAutoTokenRefresh). There is no signal from SSO into CRM, so the PRD's stale-session bugs apply to CRM:

  • A user who logs out on SSO stays logged in on CRM until the CRM token expires.
  • A user who switches account on SSO still sees the previous company in CRM.

The shared @mekari/sdk Session SDK closes this gap: every CRM page loads the SDK with the current user's SSO id; the SDK opens an iframe to account.mekari.com/sm/current, the Session Manager validates/extends the SSO session in its dedicated Redis, and the SDK dispatches logged_in, logged_out, switch_user, or server_down back to CRM. CRM must react to each consistently with its existing auth/session/routing/store patterns.

1.2 Success Criteria (CRM-FE slice)

  • CRM loads @mekari/sdk Session on all authenticated pages with the current user_sso_id, gated behind a centralized_session feature toggle.
  • CRM handles all four SDK events consistently with its current sign-out, routing, and store behavior.
  • CRM refreshes SSO session duration on user activity via session.refresh() (throttled).
  • When the Session Manager is unreachable, CRM falls back to the msli localStorage timestamp + _mekari_account cookie heuristic (2-hour idle window) and, if that fails, performs the server_down sign-out.

1.3 Non-goals / Out of scope

  • Any Session Manager / SSO / Redis / Kong-MAG backend work (owned by A&L).
  • Auto revoke of access & refresh tokens on inactivity-driven logout (PRD out-of-scope).
  • CRM backend (api.mekari.com / mobile API) changes for current_company sync — dependency, not deliverable here (see §5 Q4).
  • New product UI surfaces beyond a "user has changed" notification toast.

1.4 Design References

n/a — no Figma. This is an integration RFC with one user-visible surface: a toast on switch_user ("user has changed"). It reuses the existing notification mechanism (see Detail 2.0 Patterns). If product wants a bespoke interstitial, that is a separate design ask — raised as §5 Q5.

DocKindLinkUsed for
Centralized Web Session RFCauthoritative (driver)ConfluenceSDK contract, event names, flows, rollout

Detail 1.A — Coverage Matrices

PRD Section Coverage

PRD sectionCovered in RFCNotes
1. Overview / known issues§1.1CRM-relevant stale-session cases mapped
Success Criteria§1.2FE-slice subset; SDK/Redis criteria are A&L
Out of Scope§1.3Inherited
Dependencies (SDK, SM service, Redis)§2.0 / §5SDK consumed; SM+Redis are A&L deps
2. Technical Design — Current/Proposal§2.1–§2.2CRM current auth vs SDK proposal
How to use the SDK§2.4 (Inbound) / §2.2Mapped to CRM plugin
Local Storage (msli)§2.2 / §2.3New FE artifact
Cookies (_mekari_account)§2.0Managed by SM/SSO, read by SDK iframe — CRM does not write it
Session Manager SDK contract / events§2.4 (Inbound events)Four events handled
FE Product Integration Flows — SDK Flow§2.5 (seq)Implemented as plugin
FE Flows — Web Session Flow§2.5 / §5 Q1CRM is token-based; mapping unresolved
FE Flows — OAuth2 Authorization Code Flow§5 Q1CRM does not use authz-code today
User Logout From Product§2.5 / §2.2Reuse userLogout + redirect to sign_out
User Switch Account§2.6 (state) / §5 Q1switch_user mapping is a critical gap
Database Model (no changes)§2.3Confirmed no DDL
3. HA & Security§3FE: CSP/origin + observability
4. Rollout Plan§4CRM = step 5 (post-Launchpad pilot)
5. Open Questions§5CSP vs referrer/origin + CRM-specific
FE Implementation Scope (per product repo)§1.C / §4This RFC's core

UI / Consumer Surface Coverage

SurfaceTypeRead endpointNotes
switch_user notification toastUIn/a — covered by writesReuses notification pattern (Detail 2.0)
Sign-out redirect (logged_out / server_down)navigationn/aReuses userLogout + account.mekari.com/sign_out
Every authenticated CRM page (SDK host)runtimen/aSDK mounted globally via plugin

Role Coverage

PRD roleCRM handlingNotes
Authenticated CRM user (any role)SDK loads with their user_sso_idSession events role-agnostic
banned / suspended / freezed / expiredExisting crm-user middleware redirects unchangedSDK runs after auth guard; no role-specific session branch in PRD

Per-status authorization is unchanged by this RFC — see §2 Role × Endpoint.


Detail 1.B — Decisions Closed (index → §2 ADR blocks)

#DecisionChosenPre-baked?ADR
D1SDK delivery into CRMnpm @mekari/sdk + new Nuxt pluginpartial (PRD shows import)§2 ADR-1
D2Feature gatingReuse custom_features w/ code centralized_sessionyes (PRD names toggle)§2 ADR-2
D3SDK mount point + event wiringNew plugins/mekari-session.js, wired to $auth + store/userno§2 ADR-3
D4logged_out / server_down actionReuse existing userLogout → redirect to account.mekari.com/sign_outno§2 ADR-4
D5switch_user action (token model)Interim: treat as full sign-out + re-login; SSO-autologin path deferredno — see §5 Q1§2 ADR-5
D6msli fallback storagelocalStorage timestamp, new helperyes (PRD)§2 ADR-6
D7current_company syncDeferred — BE dependency, CRM uses teams not SSO current_companyno — see §5 Q4§2 ADR-7

Detail 1.C — Per-Story Change Map

Story (from PRD FE Implementation Scope)Layer scopeChangesAcceptance criteriaRFC anchors
S1 Add @mekari/sdk Session, load with user_sso_idFE-onlypackage.json dep; plugins/mekari-session.js (new); registered in nuxt.config.js:pluginsUnit test: plugin instantiates Session with user_sso_id from $auth.user; yarn test green§2.2, §4.C chunk 2–3
S2 Gate behind centralized_session toggleFE-onlyRead store.state.user.custom_features for code centralized_session; plugin no-ops when offTest: SDK not constructed when feature absent/false§2 ADR-2, §4.C chunk 3
S3 Handle 4 SDK eventsFE-only (Runtime/behavior)Event handlers in plugin → dispatch user/userLogout, toast, redirectTest per event: logged_in→noop+set msli; logged_out→sign-out; switch_user→sign-out+toast; server_down→sign-out§2.4 Inbound, §2.5, §2.6, §4.C chunk 4
S4 current-company syncFE + BEBlocked — no CRM current_company endpoint found; teams ≠ SSO companyn/a — covered in BE RFC [REQUIRED link] / deferred§2 ADR-7, §5 Q4
S5 Wire product logout to account.mekari.com/sign_outFE-onlyExtend store/user.js:userLogout success path to redirect to SSO sign_outTest: after sign_out POST resolves, window.locationaccount.mekari.com/sign_out§2.5, §4.C chunk 5
S6 msli fallback + server_down sign-outFE-onlyNew utils/helpers/mekari-session.js: read/write msli, 2h compare, _mekari_account presenceTest: msli<2h & cookie valid → logged_in; msli>2h or no cookie → logged_out; else server_down§2 ADR-6, §2.5, §4.C chunk 4
S7 ObservabilityFE-only (Config)Datadog RUM custom action/error on event + server_down (reuse @datadog/browser-rum)Datadog action mekari_session.event visible; yarn lint:js green§3.2, §4.C chunk 6

Every artifact above re-appears in §2 / §4. Detail 1.C is the index, not the source of truth.


2. Technical Design

2.0 Repo Reading Guide (read before writing any code)

Repo Map (slice this RFC touches)

flowchart LR
subgraph CRM[FE: local/crm — Nuxt 2 SPA]
NCONF["nuxt.config.js<br/>plugins[] (modify)"]
PLUG["plugins/mekari-session.js<br/>(new)"]
HELP["utils/helpers/mekari-session.js<br/>msli fallback (new)"]
AUTH["schemes/crmAuthScheme.js<br/>(read — auth model)"]
AHELP["utils/helpers/auth.js<br/>(read — refresh timer)"]
USTORE["store/user.js<br/>userLogout (modify)"]
UFEAT["store/user.js<br/>custom_features (read)"]
DDR["plugins/datadog-rum.js<br/>(read — RUM pattern)"]
EP["assets/variables/endpoints.js<br/>(read/extend)"]
end
subgraph EXT[READ-ONLY context — owned by A&L]
SDK["@mekari/sdk Session"]
SM["account.mekari.com/sm/current<br/>(Session Manager + iframe)"]
end
NCONF --> PLUG
PLUG --> HELP
PLUG --> SDK
SDK -. iframe .-> SM
PLUG --> USTORE
PLUG --> UFEAT
PLUG --> DDR
USTORE --> EP
AUTH -. informs .-> PLUG
AHELP -. informs .-> PLUG

Existing Code Anchors

#PathWhat to learn
1schemes/crmAuthScheme.jsCRM auth model: LocalScheme subclass; tokens in qcrm_access_token/qcrm_refresh_token cookies; logout()requestWith(logout) + $auth.reset(). CRM is token-based, not authz-code.
2utils/helpers/auth.jsrefreshToken(), setupAutoTokenRefresh() (10-min pre-expiry timer), clearAutoTokenRefresh(). The SDK must not fight this timer.
3store/user.js:120 (userLogout)Logout action: POST ${USER_URL}/sign_out, then deleteUserLocalData + deleteSsoCookies + $auth.reset(). Reuse for logged_out/server_down.
4store/user.js:144 (deleteSsoCookies)Which cookies CRM clears on logout (crm_sso_*, chat_sso_*, global_sso_*).
5store/user.js:67 (getCustomFeature)Feature toggle source: GET /users/me/feature_enabledcustom_features: [{code, enabled}]. Gate centralized_session here.
6middleware/crm-user.jsAuth guard order: runs getUserData then status redirects. SDK mounts after this.
7plugins/datadog-rum.jsRUM init pattern + env gate (DD_ENABLED). Reuse for session observability.
8nuxt.config.js:58 (plugins)Plugin registration order — ~/plugins/auth runs first; add ~/plugins/mekari-session after auth + auto-token-refresh.
9assets/variables/endpoints.jsEndpoint constant pattern (USER_URL, CRM_V28). Add SSO/SM constants here if needed.
10adapters/http/utils.js (processAuthHeaders)How crm_sso_token becomes X-Auth-Token under ENABLE_SEAMLESS_AUTH. Context for SSO-token interplay.

Source Verification

Anchor / Pattern / ContractEvidence (verified)
CRM auth = LocalScheme token modelschemes/crmAuthScheme.js:12 export default class CrmAuthScheme extends LocalScheme; login() POST + Cookies.set('qcrm_refresh_token', …) :33
logout flowschemes/crmAuthScheme.js:234 async logout()this.$auth.reset() :242; store/user.js:120 userLogout({ dispatch }), POST ${USER_URL}/sign_out :131
sign-out cookie cleanupstore/user.js:144 deleteSsoCookies() removes crm_sso_token/chat_sso_token/global_sso_token :145–155
feature toggle mechanismstore/user.js:67 getCustomFeature(); state custom_features store/user.js:12–13; mutation SET_CUSTOM_FEATURE :350
feature-check shapemiddleware/redirect-to-v3.js:49 reads store.state.user.custom_features, matches feature.code === …
refresh timerutils/helpers/auth.js:157 setupAutoTokenRefresh; :13 REFRESH_BEFORE_EXPIRY_MINUTES = 10; :248 clearAutoTokenRefresh
plugin pattern + ordernuxt.config.js:58–72 plugins[], '~/plugins/auth' first :59
RUM patternplugins/datadog-rum.js:1 import { datadogRum }; :4 if (process.env.DD_ENABLED === 'true')
endpoint constantsassets/variables/endpoints.js:1 USER_URL, :5 CRM_V28
no @mekari/sdk presentsearched package.json deps — only @mekari/pixel found; @mekari/sdk absent (verified by explorer sweep)
cookie domainschemes/crmAuthScheme.js:8 COOKIE_DOMAIN_ENV = process.env.COOKIE_DOMAIN || '.qontak.com'
CRM user_sso_id fieldUNVERIFIED/users/me response not confirmed to expose an SSO id; external_company_id exists (plugins/mixpanel.js:25) but is company, not user SSO id → §5 Q2
CRM current_company endpointUNVERIFIED / absent — only team endpoints found (store/user.js:244,261,274); no current_company → §5 Q4
centralized_session feature codeUNVERIFIED — code format is CP-QONTAKCRM-YYYY-NNNN (utils/helpers/package-features.js), but feature_enabled also uses string codes (e.g. use_central_contact_data). Exact code TBD → §5 Q3

Patterns to Follow

ConcernReference fileNote
Third-party SDK init via pluginplugins/datadog-rum.jsConditional env/feature gate; default-export init fn receiving Nuxt ctx
Plugin accessing $auth/storeplugins/auth.jswindow.$auth = app.$auth pattern; ctx access
Feature-flag readmiddleware/redirect-to-v3.js:49store.state.user.custom_features.find(f => f.code === X)
Sign-out orchestrationstore/user.js:120 userLogoutReuse, don't reinvent
External redirectmiddleware/version-switcher.js:13, middleware/redirect-to-v3.js:65window.location.href for cross-origin
Cookie accessjs-cookie (Cookies.get/set) used everywhereUse for _mekari_account read only (do not write it)
User notification toast[REQUIRED: confirm component]Per components/_general/ + user-notifications skill — verify exact toast API before drafting S3 toast

Reading Order for the Agent

  1. schemes/crmAuthScheme.js — understand the auth/session lifecycle.
  2. utils/helpers/auth.js — refresh timer; do not conflict.
  3. store/user.js (userLogout, deleteSsoCookies, getCustomFeature).
  4. middleware/crm-user.js — guard order.
  5. nuxt.config.js (plugins) — registration point + order.
  6. plugins/datadog-rum.js — SDK init + RUM pattern.
  7. assets/variables/endpoints.js — endpoint constant style.
  8. adapters/http/utils.js — SSO-token header interplay.
  9. PRD (Confluence) — SDK event contract + flows.
  10. utils/helpers/package-features.js — feature code conventions.

Existing API check

CallTagJustification
GET account.mekari.com/sm/current (via SDK iframe)new (external, A&L-owned)Not a CRM endpoint; CRM only loads the SDK which opens the iframe
GET /users/me/feature_enabledreusedstore/user.js:67 — read centralized_session toggle
POST /api/mobile/v2.7/users/sign_outreusedstore/user.js:131 — existing CRM sign-out
account.mekari.com/sign_out redirectnew (external, A&L-owned)New cross-origin redirect target on logout
current_company syncnew-with-justification / deferredNo CRM endpoint exists; PRD names an SSO endpoint hit by product BE. CRM FE cannot satisfy without BE work → §5 Q4

2.1 Current state (CRM)

CRM authenticates via POST /authentication/authenticate (crmAuthScheme.login), persists qcrm_access_token (7d) + qcrm_refresh_token (365d) cookies on .qontak.com, and auto-refreshes 10 min before expiry. Under ENABLE_SEAMLESS_AUTH, it also reads crm_sso_token/global_sso_token and injects X-Auth-Token. There is no inbound SSO session signal. Logout (store/user.js:userLogout) POSTs /users/sign_out, clears cookies/localStorage, and resets $auth — it does not redirect to SSO.

2.2 Proposal (CRM)

Add a new Nuxt plugin plugins/mekari-session.js that, when the centralized_session toggle is on:

  1. Reads the current user_sso_id from $auth.user ([REQUIRED field — §5 Q2]).
  2. Constructs new Session({ current_user: user_sso_id }) from @mekari/sdk.
  3. Subscribes to SDK events and maps them to CRM actions (§2.4 / §2.6).
  4. Calls session.refresh() (throttled) on user activity to extend the SSO session.
  5. On any SDK failure, uses the msli localStorage fallback (utils/helpers/mekari-session.js) before deciding server_down.

The plugin loads after ~/plugins/auth (so $auth.user is available) and must not interfere with setupAutoTokenRefresh.

2.3 Database Model

n/a — no database changes. CRM FE introduces client-side state only:

ArtifactStoreValueLifecycle
mslilocalStoragetimestamp of last logged_in confirmationwritten on logged_in; removed on logged_out/switch_user; read as ≤2h fallback when SM unreachable
_mekari_accountcookie (.qontak.com)SSO session idread-only in CRM — written by SSO/SM; CRM only checks presence in fallback

There are no status enums owned by CRM here; the session status lifecycle (§2.6) is a transient client state, not persisted.

2.4 APIs / Contracts

Outbound (CRM → others)

MethodEndpointAuthOwnerTagFailure behavior
GET (via SDK iframe)account.mekari.com/sm/current_mekari_account cookieA&Lnew (external)SDK retry/backoff; exhausted → server_down event
POST/api/mobile/v2.7/users/sign_outAuthorization: $auth.getToken('crm')CRM BEreused.catch → still proceed to client cleanup (existing behavior)
GET/api/mobile/v2.7/users/me/feature_enabledbearerCRM BEreusedtoggle read; on error treat feature as off (fail-closed → SDK not loaded)
Redirectaccount.mekari.com/sign_outcookieSSO (A&L)new (external)window.location.href after local cleanup
GET…/current_companyclient_credentials / meSSO (A&L)deferred§5 Q4 — not implemented in this RFC

Inbound (SDK events → CRM)

The SDK dispatches via dispatchEvent; CRM subscribes through session.on("event", (data, error) => …) (PRD usage example).

EventPayloadCRM action
logged_in{ status, user_sso_id } (matches given id)set msli = now; no navigation; emit RUM action
logged_outsession absentrun sign-out (ADR-4): store/user/userLogout → redirect account.mekari.com/sign_out
switch_usersession exists, different user_sso_idremove msli; toast "user has changed"; interim sign-out + re-login (ADR-5, §5 Q1)
server_downSDK retries exhaustedmsli fallback first; if it resolves logged_out → sign-out; else suggested sign-out (ADR-4)

2.5 Sequence Diagrams

Happy path — page load, session valid

sequenceDiagram
autonumber
participant U as User Browser
participant CRM as CRM SPA (plugin)
participant FT as feature_enabled (CRM BE)
participant SDK as @mekari/sdk Session
participant IF as iframe → account.mekari.com/sm/current
participant SM as Session Manager
participant R as SSO Redis

U->>CRM: load authenticated page
CRM->>FT: GET /users/me/feature_enabled
FT-->>CRM: centralized_session = true
CRM->>SDK: new Session({ current_user: user_sso_id })
SDK->>IF: inject iframe (_mekari_account cookie)
IF->>SM: GET /sm/current (cache ≤5s)
SM->>R: validate + update last_request_at (idle <2h)
R-->>SM: session ok, user_sso_id
SM-->>IF: render page w/ user_sso_id
IF-->>SDK: postMessage(user_sso_id)
SDK-->>CRM: event logged_in (id matches)
CRM->>CRM: localStorage msli = now
Note over CRM: no navigation; RUM action mekari_session.event=logged_in

Failure path — Session Manager unreachable (server_down)

sequenceDiagram
autonumber
participant CRM as CRM SPA (plugin)
participant SDK as @mekari/sdk Session
participant IF as iframe → /sm/current
participant LS as localStorage(msli) + cookie(_mekari_account)

CRM->>SDK: new Session({ current_user })
SDK->>IF: inject iframe
IF--xSDK: timeout / no postMessage (backoff retries)
SDK-->>CRM: event server_down
CRM->>LS: read msli + _mekari_account
alt msli age < 2h AND _mekari_account present
CRM->>CRM: treat as logged_in (degraded), keep session
Note over CRM: RUM error mekari_session.fallback=logged_in
else msli age > 2h OR cookie invalid
CRM->>CRM: server_down sign-out (ADR-4)
Note over CRM: dispatch userLogout → redirect account.mekari.com/sign_out
end

Failure path — logout from CRM (cross-origin)

sequenceDiagram
autonumber
participant U as User
participant CRM as CRM SPA
participant BE as CRM BE (/users/sign_out)
participant SSO as account.mekari.com/sign_out

U->>CRM: click logout
CRM->>BE: POST /users/sign_out (Authorization)
BE-->>CRM: 200 (or error → still proceed)
CRM->>CRM: deleteUserLocalData + deleteSsoCookies + $auth.reset()
CRM->>SSO: window.location.href = account.mekari.com/sign_out
SSO-->>U: destroy SSO session + mapping → redirect to sign in

2.6 Session State Machine (client-side)

stateDiagram-v2
[*] --> Unknown
Unknown --> LoggedIn: event logged_in (id match)
Unknown --> LoggedOut: event logged_out
Unknown --> Switched: event switch_user
Unknown --> Degraded: event server_down + msli<2h & cookie ok
Unknown --> LoggedOut: event server_down + fallback fails
LoggedIn --> LoggedOut: event logged_out
LoggedIn --> Switched: event switch_user
LoggedIn --> Degraded: event server_down (fallback ok)
Degraded --> LoggedIn: next page event logged_in
Degraded --> LoggedOut: next page event logged_out / fallback expires
Switched --> SignOut: interim (ADR-5) destroy session
LoggedOut --> SignOut
SignOut --> [*]: redirect account.mekari.com/sign_out
LoggedIn --> LoggedIn: session.refresh() (throttled, on activity)

2.7 Branch & Skip Catalog

BranchTriggerOwnerAction
Toggle offcentralized_session false/absentCRM FESDK not loaded; legacy behavior (skip)
Toggle read errorfeature_enabled failsCRM FEFail-closed: skip SDK (no regression)
logged_in id matchnormalCRM FENo-op + msli refresh
Degraded keep-aliveserver_down + msli<2h + cookie okCRM FEKeep session (no sign-out)

2.8 Technical Decisions (ADR)

Minimum-coverage map: Storage→ADR-6; Sync/async→ADR-3; Caching→ADR-6 (msli 2h

  • SM 5s, SM-owned); Third-party→ADR-1; Consistency→ADR-3; Multi-tenancy→ADR-5 (user_sso_id compare); Reuse/new→ADR-4.

ADR-1 — SDK delivery into CRM

  • Context: PRD provides @mekari/sdk Session (CJS+ESM) and an SDK URL account.mekari.com/sm/sdk.js. CRM is Nuxt 2 SPA, plugins for third-party SDKs (plugins/datadog-rum.js).
  • Options: (a) npm @mekari/sdk imported in a Nuxt plugin; (b) <script src=account.mekari.com/sm/sdk.js> runtime tag.
  • Decision: (a) npm package + plugins/mekari-session.js.
  • Rationale: matches the PRD usage example (import { Session }), the existing plugin pattern, tree-shaking, and testability (mockable import). Pre-baked by author (import form).
  • Consequences: new pinned dependency; must confirm package exists in Mekari npm registry (§5 Q6). The SDK still injects the iframe to account.mekari.com at runtime, so origin/CSP policy (§3.1) still applies.
  • Reversibility: high — remove plugin + dep; toggle off instantly.

ADR-2 — Feature gating

  • Context: PRD gates behavior behind toggle centralized_session. CRM has custom_features from /users/me/feature_enabled.
  • Options: (a) reuse custom_features; (b) new env flag; (c) new packageFeatures entry.
  • Decision: (a) reuse custom_features, check code centralized_session.
  • Rationale: server-driven, per-company/app rollout (PRD step 5) matches feature_enabled semantics; no new mechanism. Pre-baked.
  • Consequences: exact code string must be confirmed (§5 Q3); fail-closed on read error.
  • Reversibility: high — server toggles off.

ADR-3 — SDK mount point & event wiring

  • Context: SDK must run on all authenticated pages with $auth.user available, without fighting setupAutoTokenRefresh.
  • Options: (a) Nuxt plugin after ~/plugins/auth; (b) per-page mixin; (c) middleware.
  • Decision: (a) plugin, registered after auth+auto-token-refresh in nuxt.config.js:plugins.
  • Rationale: single global init (like RUM), guaranteed $auth ready, one event subscription. Async/event-driven (no blocking); consistency model is eventual — session re-checked per page load + on activity refresh.
  • Consequences: SPA route changes don't reload the plugin; need an on-activity / per-navigation session.refresh() hook (router afterEach).
  • Reversibility: high.

ADR-4 — logged_out / server_down action

  • Context: PRD: product sign-out flow on logged_out; suggested sign-out on server_down.
  • Options: (a) reuse store/user/userLogout; (b) bespoke teardown.
  • Decision: (a) reuse userLogout, then redirect to account.mekari.com/sign_out.
  • Rationale: userLogout already clears cookies/localStorage/$auth; only the SSO redirect is new (S5). No duplication.
  • Consequences: adds cross-origin redirect to logout; verify it doesn't break existing in-app logout callers (§5 Q7).
  • Reversibility: medium — redirect target behind same toggle.

ADR-5 — switch_user action under CRM's token model

  • Context: PRD web-session flow for switch_user = destroy product session → redirect to product sign-in → SSO autologin (valid _mekari_account) → back → new product session → get current company. CRM has no SSO-autologin redirect today (crm-user redirects to crmV1Host/login), and CRM is token-based, not authz-code.
  • Options: (a) full implementation of SSO autologin round-trip (needs BE + SSO callback CRM does not have); (b) interim: treat switch_user as sign-out + toast "user has changed", let user re-login as the new account; (c) silently swap tokens (rejected — no mechanism, security risk).
  • Decision: (b) interim sign-out + toast; (a) deferred pending SSO autologin contract.
  • Rationale: (a) requires an SSO callback route + token re-issue path that does not exist in CRM and is unspecified for the token model — implementing it blind would hallucinate the contract. (b) is correct and safe.
  • Consequences: worse UX than PRD's seamless switch (user re-enters credentials or hits SSO login). Must be called out to product.
  • Reversibility: high — upgrade to (a) when SSO autologin path is defined.
  • Status: depends on §5 Q1 [critical].

ADR-6 — msli fallback storage

  • Context: PRD: msli localStorage timestamp as SM-down fallback.
  • Options: (a) localStorage timestamp + _mekari_account presence check; (b) cookie; (c) Vuex (lost on reload).
  • Decision: (a), in new utils/helpers/mekari-session.js.
  • Rationale: survives reloads, matches PRD, pure + unit-testable.
  • Consequences: localStorage clears must be added to deleteUserLocalData (clear msli on logout).
  • Reversibility: high.

ADR-7 — current_company sync

  • Context: PRD products sync current company after session via SSO current_company (BE, client_credentials). CRM uses teams (/users/crm_teams, /crm/teams), and no current_company endpoint or field was found.
  • Options: (a) implement once CRM BE exposes current_company; (b) map to CRM team context; (c) defer.
  • Decision: (c) defer — out of FE-deliverable scope; needs BE RFC.
  • Rationale: no in-repo contract exists; inventing one violates anti-hallucination. Whether CRM even has a per-company switch equivalent is unconfirmed.
  • Consequences: after switch_user interim sign-out + re-login, company context resolves through CRM's normal login — acceptable for interim.
  • Reversibility: n/a (deferred).
  • Status: §5 Q4 [critical].

2.9 Role × Endpoint Authorization

Rolefeature_enabledsign_outSDK iframe
Any authenticated userread own toggleown sessionown _mekari_account
banned/suspended/freezed/expiredguarded by crm-user before SDKsameunchanged

n/a — no new authorization surface; SDK is per-user via user_sso_id compare.


3. High-Availability & Security

3.1 Security — SDK origin policy

The SDK injects an iframe to account.mekari.com and exchanges postMessage. CRM (the SDK host) must be hardened. PRD offers two mechanisms (PRD §3 Security Implications), and CRM currently has no CSP configured (nuxt.config.js head has meta/link only — verified). CRM-side requirements:

  • postMessage origin validation (mandatory): the plugin must verify event.origin === 'https://account.mekari.com' before trusting any SDK message. (If the SDK already enforces this internally, confirm and document.)
  • CSP [REQUIRED decision — §5 Q8]: add frame-src/child-src https://account.mekari.com and script-src for the SDK origin. Since CRM is SPA-only with no server headers in nuxt.config.js, CSP must be delivered by the CDN/edge (out of repo) or a <meta http-equiv>mechanism unconfirmed.
  • Never write _mekari_account from CRM; read presence only.

3.2 Observability (FE)

Reuse @datadog/browser-rum (plugins/datadog-rum.js). Emit:

  • RUM custom action mekari_session.event with { event, matched }.
  • RUM error on server_down and on postMessage origin mismatch.
  • Structured console-free logging via existing RUM (no new console.log).

Metric naming follows existing service: qontak-crm-frontend RUM convention (plugins/datadog-rum.js:9). Backend p95/RPS alerts (PRD §3) are A&L-owned.

3.3 HA / Performance (FE slice)

CRM adds one async, non-blocking SDK init per page; the toggle and msli fallback ensure CRM degrades gracefully if /sm/current is slow/down (no blocking render). The 6k RPS / 50ms latency targets (PRD §3) are SM-side, not CRM-FE.


4. Backwards Compatibility and Rollout Plan

4.A Rollout

StageAudienceGo/No-go evidence
0. Build behind togglenone (toggle off)Plugin + dep merged; centralized_session off everywhere; no behavior change
1. Internal pilotCRM internal companyToggle on for 1 company; RUM mekari_session.event flowing; zero sign-out-loop errors
2. Gradual% of companieserror rate flat; no spike in server_down sign-outs
3. GAalltoggle default on

CRM rollout is after the PRD's Launchpad pilot (PRD §4 step 5) — CRM's domain must be added to the SDK CSP frame-ancestors/origin whitelist by A&L first.

4.B Backwards compatibility & Verification (pre-merge)

Pre-merge commands (in order, sourced from package.json scripts):

yarn lint:js # eslint . --ext .js,.vue --cache
yarn test # jest test -u
yarn test-file tests/plugins/mekari-session.spec.js
yarn test-file tests/utils/helpers/mekari-session.spec.js
yarn build # nuxt build (verify SDK import resolves)

lint:js, test, test-file (jest), build (nuxt build) verified in package.json scripts (explorer-confirmed).

4.C Agent Execution Plan

#ChunkFilesCommandsAcceptance criteria (assertable)
1Add dependencypackage.json (+ yarn.lock)yarn add @mekari/sdk (pin)@mekari/sdk in deps; yarn install --frozen-lockfile passes — blocked on §5 Q6 (registry availability)
2msli fallback helperutils/helpers/mekari-session.js (new); tests/utils/helpers/mekari-session.spec.js (new)yarn test-file tests/utils/helpers/mekari-session.spec.jsTests: msli<2h+cookie→logged_in; >2h or no cookie→logged_out; missing→server_down
3SDK plugin + toggle gateplugins/mekari-session.js (new); nuxt.config.js (plugins[]); tests/plugins/mekari-session.spec.js (new)yarn test-file tests/plugins/mekari-session.spec.js; yarn lint:jsTest: toggle off → no Session; on → Session built w/ user_sso_id; event.origin validated
4Event handlersplugins/mekari-session.js; store/user.js (clear msli in deleteUserLocalData)yarn testPer-event tests pass (logged_in/out/switch/server_down) per §2.4; msli cleared on logout
5Logout → SSO redirectstore/user.js:userLogout (success path)yarn test-file tests/store/user.spec.jsTest: after sign_out resolves, window.location.href set to account.mekari.com/sign_out
6Observabilityplugins/mekari-session.js (RUM calls)yarn lint:js; yarn buildRUM action mekari_session.event emitted in handler unit test (mock datadogRum)

Chunk order respects deps: helper (2) before plugin (3); plugin before handlers (4); store edits (4,5) tested in isolation.

4.D Verification & Rollback Recipe

Post-deploy signals:

  • Datadog RUM action mekari_session.event present for piloted company.
  • No spike in CRM sign-out / login redirects vs baseline (RUM view.error/session count).
  • server_down fallback rate within expected (mekari_session.fallback).

Rollback (numbered, agent-executable):

  1. Set centralized_session toggle off for affected company/all (server-side feature_enabled) — instant, no deploy. Primary lever.
  2. If code-level revert needed: revert the PR adding plugins/mekari-session.js
    • nuxt.config.js plugin entry; redeploy.
  3. Confirm CRM auth/logout behaves as pre-RFC (token login works, in-app logout no longer redirects to account.mekari.com/sign_out).
  4. Verify RUM session/error counts return to baseline.

5. Concern, Questions, or Known Limitations

#SeverityQuestion / limitationBlocks
Q1[critical]switch_user proper flow: CRM is token-based, not OAuth2 authz-code, and has no SSO-autologin callback route. Which PRD flow applies to CRM, and what is the SSO-autologin contract for token products? Interim is sign-out+re-login (ADR-5).§7 yes
Q2[critical]Source of user_sso_id in CRM: does /users/me (crmAuthScheme.fetchUser) expose a user SSO id? Only external_company_id (company) verified. SDK input cannot be filled without this.§7 yes
Q3[critical]Exact centralized_session feature code/string in feature_enabled (string code vs CP-QONTAKCRM-YYYY-NNNN).§7 yes
Q4[critical]current_company sync for CRM: no endpoint/field exists; CRM uses teams. Is there a CRM company-context equivalent, and is a BE RFC needed? (ADR-7 deferred)§7 yes
Q5[important]switch_user toast: confirm exact CRM notification component/API (components/_general/ + user-notifications skill) before drafting.chunk 4
Q6[important]Is @mekari/sdk published to the Mekari/private npm registry CRM can install from? CJS+ESM build confirmed by PRD, registry not.chunk 1
Q7[important]All existing in-app callers of userLogout — will adding the account.mekari.com/sign_out redirect break any flow expecting to stay in-app (e.g. account-switch, embed layout)?chunk 5
Q8[important]CSP delivery: CRM has no server/header CSP (nuxt.config.js). CSP vs referrer/origin (PRD open Q) + where the header is set (edge/CDN vs meta). Infosec decision.§3.1
Q9[nice-to-have]session.refresh() throttle interval ("TBD" in PRD) — define for CRM (per-navigation vs time-based).

6. Comment logs

DateAuthorComment
2026-06-27CRM Frontend (draft)Initial CRM-FE integration draft grounded against schemes/crmAuthScheme.js, store/user.js, utils/helpers/auth.js, nuxt.config.js. 4 critical unknowns block execution (token model vs authz-code, user_sso_id source, toggle code, current_company).

7. Ready for agent execution

Ready for agent execution: no

Failing gates:

  • B2 / F (contracts): SDK input user_sso_id unverified in CRM /users/me (Q2). Cannot construct Session correctly.
  • D2 (decision closure): switch_user real flow unresolved for CRM's token model (Q1); current_company deferred without a BE contract (Q4).
  • C2 (anti-hallucination): centralized_session exact code not verified (Q3) — left as placeholder rather than invented.

Resolve Q1–Q4 ([critical]) to flip the marker to yes. Chunks 2, 3, 6 (msli helper, plugin scaffold + toggle gate, observability) are independently executable today; chunks 1, 4, 5 depend on the critical answers.