Task Breakdown — Centralized Web Session (CRM Frontend Integration of @mekari/sdk Session)
Source RFC:
crm/centralized-web-session.md(status: RFC — not yet Ready for agent execution) Slicing mode: Vertical (1 task = 1 story end-to-end: UI + integration + tests; merging rule applied) Blocked-task handling: Full-picture — blocked tasks shown inline with unblock conditions; full-scope Skipped table at end. Target repo (verified):/Users/mekari/Documents/crm(Nuxt 2 SPA). Test runner: jest (yarn test=jest test -u; single file =yarn test-file <path>). Lint:yarn lint:js. Build:yarn build(nuxt build).
Reconnaissance notes (grounding for every path below)
| Check | Result |
|---|---|
| Test runner | jest, not vitest. Run-one: yarn test-file <path>; all: yarn test. Lint: yarn lint:js; build: yarn build. (verified in package.json scripts) |
| Test layout | tests/ mirrors source dirs (tests/store/, tests/utils/helpers/, tests/middleware/, …). Specs are *.spec.js, not co-located, not TypeScript. |
tests/plugins/ | Does not exist yet — must be created for the plugin spec. Verified: no tests/plugins/ dir, no existing plugin test anywhere. |
@mekari/sdk | Absent from package.json (only @mekari/pixel@^1.1.14). Net-new dependency — install blocked on registry availability (Q6). |
@datadog/browser-rum | Present @^4.34.0. datadogRum imported in plugins/datadog-rum.js; addAction/addError not yet used anywhere in the repo. |
| Cited source files | All verified present: schemes/crmAuthScheme.js, utils/helpers/auth.js, store/user.js, middleware/crm-user.js, plugins/datadog-rum.js, nuxt.config.js, assets/variables/endpoints.js, adapters/http/utils.js, plugins/auth.js, middleware/redirect-to-v3.js, plugins/mixpanel.js, utils/helpers/package-features.js. |
store/user.js anchors | userLogout:120, deleteSsoCookies:144, deleteUserLocalData:157, getCustomFeature:67, custom_features state:12, SET_CUSTOM_FEATURE:349. All confirmed. |
nuxt.config.js plugins | Block at line 58. Order: ~/plugins/auth (59) → ~/plugins/auto-token-refresh (60) → … ~/plugins/datadog-rum (66). New plugin registers after auto-token-refresh. |
| Refresh timer | utils/helpers/auth.js: setupAutoTokenRefresh:157, REFRESH_BEFORE_EXPIRY_MINUTES=10:13, clearAutoTokenRefresh:248. SDK must not fight this. |
| Toast component | Unverified — check repo. No global $toast plugin found. Existing toast pattern = a toastCallback({...}) injected into setupAutoTokenRefresh (utils/helpers/auth.js:203). Exact switch_user toast API is genuinely unconfirmed (RFC Q5). |
user_sso_id | Unverified / absent. Only external_company_id exists (plugins/mixpanel.js:25, company-level). No user SSO id field confirmed (RFC Q2). |
_mekari_account cookie | Read via js-cookie Cookies.get (already imported in schemes/crmAuthScheme.js:2). CRM reads presence only, never writes. |
Effort Summary
Vertical mode — one row per story task. Days are man-days. QA = 20–25% of dev effort for user-facing behaviour, min 0.5; 0 for internal-only.
| Task | Story | Status | FE days | BE days | QA days | Total |
|---|---|---|---|---|---|---|
1. msli fallback helper | S6 | ✅ Actionable | 1.5 | — | 0.5 | 2.0 |
| 2. SDK plugin scaffold + toggle gate + origin check | S1, S2 | ⚠️ Partially blocked | 2.0 | — | 0.5 | 2.5 |
| 3. SDK event handlers (4 events) + msli cleanup | S3 | ⚠️ Partially blocked | 2.0 | — | 0.5 | 2.5 |
4. Logout → SSO sign_out redirect | S5 | ✅ Actionable | 1.0 | — | 0.5 | 1.5 |
| 5. Datadog RUM observability | S7 | ✅ Actionable | 0.5 | — | 0 | 0.5 |
6. Add @mekari/sdk dependency | S1 (dep) | 🚫 Blocked | 0.5 | — | 0 | 0.5 |
7. switch_user proper SSO-autologin flow | S3/Q1 | 🚫 Blocked | 3.0 | — | 0.5 | 3.5 |
8. current_company sync | S4 | 🚫 Blocked | 1.0 | 2.0 | 0.5 | 3.5 |
| Grand total | 11.5 | 2.0 | 3.5 | 17.0 | ||
| Actionable-now subtotal (Tasks 1–5) | 7.0 | — | 2.0 | 9.0 |
Confidence: low. Four
[critical]open questions (Q1–Q4) gate the RFC's own "Ready for agent execution: no" marker. The biggest movers: (a)user_sso_idsource is unverified (Q2) — without it theSessionconstructor in Task 2 can't be filled, so Task 2 ships behind a stub; (b) the exactcentralized_sessionfeature code string is unverified (Q3); (c) the realswitch_userflow (Task 7) andcurrent_companysync (Task 8) have no in-repo contract and are estimated coarsely. Tasks 1, 4, 5 are genuinely actionable today; Tasks 2 and 3 are buildable as shells now with the SDK call/user_sso_idstubbed. The toast component (Q5) is unconfirmed and will move Task 3 slightly.
Task 1: [FE] msli localStorage fallback helper (S6)
A CRM user keeps their session (degraded) when the Session Manager is briefly unreachable, instead of being wrongly signed out — and is signed out only when the fallback genuinely expires.
Status: ✅ Actionable — pure client-side logic, no SDK and no user_sso_id needed; _mekari_account is read-only via the already-imported js-cookie.
Design reference: n/a — no Figma (RFC §1.4; this task has no visible UI).
What to build
A new pure helper module that reads/writes the msli localStorage timestamp and reads _mekari_account cookie presence, and exposes a function that, given "now", returns the fallback decision: logged_in (msli < 2h and cookie present), logged_out (msli > 2h or cookie absent), or server_down (no msli at all). This is the decision engine consumed by Task 3's server_down handler.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| create | utils/helpers/mekari-session.js | setMsli(now), clearMsli(), readMsli(), getFallbackDecision(now, { idleWindowMs = 2*60*60*1000 }) returning 'logged_in' | 'logged_out' | 'server_down'; reads _mekari_account via Cookies.get |
| create | tests/utils/helpers/mekari-session.spec.js | Unit tests for each decision branch + set/clear/read round-trip |
File path rule:
utils/helpers/mekari-session.jsmatches the existingutils/helpers/convention (siblings:auth.js,package-features.js). Spec mirrors totests/utils/helpers/per the verified layout (tests/utils/helpers/auth.spec.jsexists).
Implementation steps
- Explore the codebase area — Open
utils/helpers/auth.js(verified) andtests/utils/helpers/auth.spec.jsto copy the module style (plain ES exports,Datemath likesetupAutoTokenRefresh) and the jest spec style. Openschemes/crmAuthScheme.js:2to copy theimport Cookies from 'js-cookie'form for the cookie read. - Write failing tests (red) — Create
tests/utils/helpers/mekari-session.spec.js. Cover: msli age < 2h + cookie present →'logged_in'; msli age > 2h →'logged_out'; cookie absent →'logged_out'; no msli key →'server_down';setMsli/clearMsli/readMsliround-trip. MocklocalStorageandCookies.get. Runyarn test-file tests/utils/helpers/mekari-session.spec.jsand confirm failure. - Scaffold — Create
utils/helpers/mekari-session.jswith the four exported functions; constantsMSLI_KEY = 'msli',MEKARI_ACCOUNT_COOKIE = '_mekari_account',IDLE_WINDOW_MS = 2 * 60 * 60 * 1000. - Wire state —
readMsliparseslocalStorage.getItem('msli')to a number;getFallbackDecisioncomparesnow - msliagainstidleWindowMsandCookies.get('_mekari_account')presence. - Implement behavior — Fill the branch logic exactly per §2.5 failure-path diagram and §2.4
server_downrow. - Go green —
yarn test-file tests/utils/helpers/mekari-session.spec.jsuntil all pass. - Quality gate —
yarn lint:js && yarn build.
Acceptance criteria
- msli age < 2h and
_mekari_accountpresent → returnslogged_in. - msli age > 2h or
_mekari_accountabsent → returnslogged_out. - no
mslikey present → returnsserver_down. -
setMsli(now)writes a numeric timestamp;clearMsli()removes the key; both verified by re-reading.
Test strategy
Pure-function unit tests. Mock window.localStorage (jest jsdom default) and stub Cookies.get to return/omit _mekari_account. Key assertion: each of the three decision strings is produced for its exact precondition; freeze now to make the 2h boundary deterministic (test exactly at 2h ± 1ms).
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 1.5 |
| Backend | — |
| QA | 0.5 |
| Total | 2.0 |
Assumptions: pure helper, no SDK dependency, reuses existing
js-cookieimport; jsdomlocalStorageavailable in jest (verified test env). No new package.
Run to verify
yarn test-file tests/utils/helpers/mekari-session.spec.js && yarn lint:js
Depends on
- None. This is the recommended first task.
Task 2: [FE] SDK plugin scaffold + centralized_session toggle gate + postMessage origin validation (S1, S2)
When the
centralized_sessiontoggle is on for the user's company, CRM loads the@mekari/sdkSessionon every authenticated page with the current user's SSO id; when the toggle is off (or unreadable), CRM behaves exactly as today.
Status: ⚠️ Partially blocked — the plugin shell, toggle gate, registration order, and event.origin guard are all buildable now with the Session constructor and user_sso_id stubbed. Blocked pieces: real new Session({...}) import awaits Task 6 (@mekari/sdk not installed, Q6); the user_sso_id field source is unverified (Q2); the exact toggle code string is unverified (Q3). Build the shell with these stubbed and a clearly-marked TODO.
Design reference: n/a — design pending; no Figma (RFC §1.4). This task has no visible UI surface (the toast lives in Task 3).
What to build
A new Nuxt plugin that runs after ~/plugins/auth + ~/plugins/auto-token-refresh, reads the centralized_session toggle from store.state.user.custom_features, no-ops when the toggle is off/absent/errored (fail-closed), and when on, constructs the SDK Session with user_sso_id and registers a single guarded message listener that rejects any postMessage whose event.origin !== 'https://account.mekari.com'. Event-to-action mapping is added in Task 3.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| create | plugins/mekari-session.js | Default-export init fn (ctx); reads toggle via ctx.store.state.user.custom_features.find(f => f.code === 'centralized_session' && f.enabled); stub getUserSsoId(ctx) (TODO Q2); guarded Session construction (stubbed import until Task 6); origin-validated listener registration |
| extend | nuxt.config.js | Add '~/plugins/mekari-session' to plugins[] (line 58 block) after '~/plugins/auto-token-refresh' |
| create | tests/plugins/mekari-session.spec.js | New file (and tests/plugins/ dir — does not exist yet); tests toggle-on/off/error gating, origin validation, no Session when off |
File path rule:
plugins/mekari-session.jsmatches the flatplugins/convention (siblings:datadog-rum.js,mixpanel.js,auth.js).tests/plugins/must be created — it does not exist in the repo today (verified).'~/plugins/mekari-session'registration string matches the existing'~/plugins/...'entries innuxt.config.js:58.
Implementation steps
- Explore the codebase area — Open
plugins/datadog-rum.js(verified) for the conditional-gate + default-export-init-fn pattern (if (process.env.DD_ENABLED === 'true')), andplugins/auth.jsfor$auth/storeaccess via the Nuxt ctx. Openmiddleware/redirect-to-v3.js:49for the exactstore.state.user.custom_features.find(f => f.code === …)read shape. Confirmnuxt.config.js:58–71plugin order. - Write failing tests (red) — Create
tests/plugins/thentests/plugins/mekari-session.spec.js. Mock the ctx (store.state.user.custom_features,$auth.user). Assert: toggle off/absent →Sessionconstructor (mocked) never called; toggle-read throws → noSession(fail-closed); toggle on →Sessionconstructed once with the stubbed sso id; amessageevent with wrongoriginis ignored, correct origin is accepted. Runyarn test-file tests/plugins/mekari-session.spec.js, confirm failure. - Scaffold — Create
plugins/mekari-session.jswith the default-export init fn, agetToggle(ctx)helper, agetUserSsoId(ctx)stub returningctx.$auth.user?.user_sso_idwith a// TODO Q2: confirm fieldcomment, and anisTrustedOrigin(origin)guard. - Wire state — Read the toggle from
store.state.user.custom_features(same shape asredirect-to-v3.js:49). Guard the whole body intry/catch→ fail-closed. - Implement behavior — When toggle on:
// import { Session } from '@mekari/sdk'(leave import commented + a mockable factory seam until Task 6), constructnew Session({ current_user: ssoId }), attach awindow.addEventListener('message', …)(orsession.on) that drops untrusted origins. Do not add event→action mapping here (Task 3). - Go green —
yarn test-file tests/plugins/mekari-session.spec.jsuntil pass. - Quality gate —
yarn lint:js && yarn build(build will pass only once the SDK import is stubbed/commented — uncomment in Task 6).
Acceptance criteria
- Toggle absent/false →
Sessionis not constructed; no listener attached; no console noise. -
feature_enabledread error → fail-closed (noSession), no regression to existing auth. - Toggle on →
Sessionconstructed exactly once withcurrent_user= the resolved sso id. -
messageevents withorigin !== 'https://account.mekari.com'are ignored. - Plugin is registered in
nuxt.config.jsafterauto-token-refresh; does not call intosetupAutoTokenRefresh. - (pending Q2)
user_sso_idresolves from a confirmed/users/mefield — currently stubbed. - (pending Q3) the
centralized_sessioncode string matches the realfeature_enabledvalue.
Test strategy
Plugin unit test with a hand-rolled ctx mock and a mocked Session factory. Key mock: the @mekari/sdk Session (jest jest.mock once the dep lands, or a injected factory until then). Key assertions: gating matrix (off/error/on) and origin rejection. Stub getUserSsoId to a fixture value so the constructor arg is assertable without Q2 resolved.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 2.0 |
| Backend | — |
| QA | 0.5 |
| Total | 2.5 |
Assumptions: reuses the
datadog-rumplugin pattern and theredirect-to-v3feature-read shape; SDK import stubbed behind a factory seam so the shell builds before Task 6;user_sso_idand toggle code stubbed pending Q2/Q3.tests/plugins/is net-new.
Run to verify
yarn test-file tests/plugins/mekari-session.spec.js && yarn lint:js
Depends on
- [Task 1] — the plugin will call the msli helper from its handlers (handlers land in Task 3).
- [External:
@mekari/sdkpackage — Task 6 (Q6)] for the real import. - [External:
/users/meuser_sso_idfield — Q2 (pending)] and [centralized_sessioncode string — Q3 (pending)].
Task 3: [FE] SDK event handlers — logged_in / logged_out / switch_user / server_down + msli cleanup (S3)
CRM reacts correctly to every SSO session signal: refreshes its session marker on
logged_in, signs out onlogged_out, signs out + toasts "user has changed" onswitch_user(interim), and onserver_downuses the msli fallback before deciding whether to sign out.
Status: ⚠️ Partially blocked — all four handlers and the msli-cleanup edit are buildable now against mocked SDK events. Blocked: the real SDK event subscription depends on Task 6; the switch_user interim path is correct now but the proper SSO-autologin flow is deferred (Task 7, Q1); the exact toast component/API is unconfirmed (Q5).
Design reference: n/a — design pending; reuses the existing notification mechanism, exact toast component unverified (RFC Detail 2.0 Patterns marks it [REQUIRED: confirm component]; Q5). [unverified — check repo].
What to build
The event→action mapping inside plugins/mekari-session.js: logged_in → setMsli(now) + RUM action, no navigation; logged_out → dispatch user/userLogout then redirect to account.mekari.com/sign_out (the redirect itself is implemented in Task 4); switch_user → clearMsli() + toast "user has changed" + interim sign-out; server_down → call Task 1's getFallbackDecision, then keep-session (degraded) or sign out. Also clear msli inside store/user.js:deleteUserLocalData so logout wipes the marker.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| extend | plugins/mekari-session.js | Add the four session.on(event, …) handlers mapping to msli helper + store dispatch + toast + redirect |
| extend | store/user.js (deleteUserLocalData:157) | Clear msli localStorage key on logout (call clearMsli() or localStorage.removeItem('msli')) |
| extend | tests/plugins/mekari-session.spec.js | Add per-event tests (4 events) |
| extend | tests/store/user.spec.js (if exists; else create) | Assert msli cleared in deleteUserLocalData |
File path rule:
store/user.jsand itsdeleteUserLocalDataaction (line 157) verified.tests/store/exists (many*.spec.js); confirm whether auser.spec.jsalready exists there before creating —[verify exact store spec filename in repo].
Implementation steps
- Explore the codebase area — Re-open
plugins/mekari-session.js(from Task 2) andstore/user.js: readuserLogout(line 120) anddeleteUserLocalData(line 157) to see the existing localStorage-clearing style. For the toast, first resolve Q5: searchcomponents/_general/and theuser-notificationsskill, and look at howsetupAutoTokenRefresh'stoastCallback(utils/helpers/auth.js:203) is wired by its caller — reuse that exact mechanism rather than inventing a$toast.[unverified — check repo] - Write failing tests (red) — Extend
tests/plugins/mekari-session.spec.jswith one test per event:logged_in→setMslicalled, no navigation;logged_out→userLogoutdispatched;switch_user→clearMsli+ toast + sign-out;server_down→getFallbackDecisionconsulted, keep-alive when it returnslogged_in, sign-out whenlogged_out. Add atests/store/...assertion thatdeleteUserLocalDataremovesmsli. Run, confirm red. - Scaffold — Add a
handlers = { logged_in, logged_out, switch_user, server_down }map in the plugin; subscribe each via the SDK'ssession.on('event', (data, error) => …)(mocked until Task 6). - Wire state — Import
setMsli/clearMsli/getFallbackDecisionfromutils/helpers/mekari-session.js(Task 1). Dispatchstore.dispatch('user/userLogout')for sign-out events. - Implement behavior — Fill each handler per §2.4 Inbound table and the §2.6 state machine. For
switch_useruse the interim ADR-5 path (sign-out + toast); leave a// TODO Q1: proper SSO-autologin flow → Task 7. AddclearMsli()intostore/user.js:deleteUserLocalData. - Go green —
yarn test(or the twotest-filespecs) until pass. - Quality gate —
yarn lint:js && yarn build.
Acceptance criteria
-
logged_in(id matches) →msliset to now; no navigation occurs. -
logged_out→user/userLogoutdispatched (redirect added in Task 4). -
switch_user→mslicleared, "user has changed" toast shown, interim sign-out triggered. -
server_down→ fallback consulted;logged_indecision keeps session (degraded),logged_outdecision signs out. -
deleteUserLocalDataremoves themslikey on every logout. - (pending Q5) toast uses the confirmed CRM notification component.
- (pending Q1)
switch_useris interim sign-out only — proper flow is Task 7.
Test strategy
Drive the mocked Session to emit each event and assert the resulting store dispatch / msli mutation / toast call. Key mock: a fake session emitter + spies on setMsli/clearMsli/store.dispatch/the toast callback. Key assertion: server_down branches exactly on getFallbackDecision's return value.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 2.0 |
| Backend | — |
| QA | 0.5 |
| Total | 2.5 |
Assumptions: reuses Task 1's helper and existing
userLogout; toast reuses the existing notification mechanism (unconfirmed component — Q5); interimswitch_useronly. No new package.
Run to verify
yarn test-file tests/plugins/mekari-session.spec.js && yarn lint:js
Depends on
- [Task 1] (msli helper), [Task 2] (plugin shell + subscription seam), [Task 4] (the redirect that
logged_out/switch_user/server_downsign-out reuses). - [External:
@mekari/sdk— Task 6] for the real subscription; [Q5 toast component (pending)].
Task 4: [FE] Wire product logout to account.mekari.com/sign_out (S5)
When a CRM user logs out (in-app or via an SDK sign-out event), the SSO session is also destroyed — the browser is redirected to
account.mekari.com/sign_outafter CRM's local cleanup completes.
Status: ✅ Actionable — extends an existing, verified action; no SDK or user_sso_id needed. The only caveat (Q7) is a regression-check, not a blocker.
Design reference: n/a — navigation only, no Figma.
What to build
Extend the success path of store/user.js:userLogout so that, after the sign_out POST resolves and local cleanup (deleteUserLocalData + deleteSsoCookies + $auth.reset()) runs, the browser is redirected via window.location.href = 'https://account.mekari.com/sign_out'. Guard the redirect behind the centralized_session toggle so legacy logout is unchanged when the feature is off.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| extend | store/user.js (userLogout:120, success path after :133–134) | After local cleanup, if centralized_session on, window.location.href = 'https://account.mekari.com/sign_out' |
| extend/create | tests/store/user.spec.js (verify filename) | Assert window.location.href set to the SSO sign_out URL after sign_out resolves; assert no redirect when toggle off |
File path rule:
store/user.js:userLogoutverified at line 120, cleanup at 133–134. The external-redirect-via-window.location.hrefpattern is verified inmiddleware/version-switcher.js:13andmiddleware/redirect-to-v3.js:65. Test pathtests/store/user.spec.js[verify exact filename in tests/store/].
Implementation steps
- Explore the codebase area — Open
store/user.jsand readuserLogout(120) end-to-end: it POSTs${USER_URL}/sign_outthen dispatchesdeleteUserLocalData+deleteSsoCookies. Openmiddleware/redirect-to-v3.js:65to copy thewindow.location.hrefcross-origin redirect idiom. Resolve Q7 first: grep all callers ofuserLogout(account-switch, embed layouts) to confirm none expect to stay in-app. - Write failing tests (red) — In
tests/store/user.spec.js: mock thesign_outrequest to resolve, setcentralized_sessionon, assertwindow.location.href === 'https://account.mekari.com/sign_out'afteruserLogout; toggle off → assert no redirect. Mockwindow.location. Run, confirm red. - Scaffold — Add a redirect line guarded by the toggle read in the
userLogoutsuccess continuation. - Wire state — Read the toggle from
state.custom_features(same shape as elsewhere) inside the action. - Implement behavior — Set
window.location.hrefonly after cleanup; keep the existing.catchbehaviour (proceed to cleanup even if POST errors) intact. - Go green —
yarn test-file tests/store/user.spec.js. - Quality gate —
yarn lint:js && yarn build.
Acceptance criteria
- After
sign_outPOST resolves and cleanup runs,window.location.hrefis set tohttps://account.mekari.com/sign_outwhencentralized_sessionis on. - When the toggle is off,
userLogoutbehaves exactly as pre-RFC (no external redirect). - Existing
.catchpath (POST error → still clean up) is preserved. - (Q7) No existing in-app
userLogoutcaller is broken by the redirect (verified by caller audit, behind the toggle).
Test strategy
Store-action unit test with mocked request and mocked window.location. Key mock: the sign_out HTTP call (resolve + reject cases). Key assertion: redirect URL set exactly once, only when toggle on, only after cleanup.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 1.0 |
| Backend | — |
| QA | 0.5 |
| Total | 1.5 |
Assumptions: reuses existing
userLogout+ verifiedwindow.location.hrefredirect idiom; redirect gated by the toggle so it's a no-regression change; Q7 is a caller audit, not new code.
Run to verify
yarn test-file tests/store/user.spec.js && yarn lint:js
Depends on
- None to start (independent of the SDK). Tasks 3's sign-out events reuse this redirect, so land it before/with Task 3.
Task 5: [FE] Datadog RUM observability for session events (S7)
Operators can see session-event volume, fallback rate, and origin-mismatch errors in Datadog for the piloted company, so the rollout can be monitored and rolled back on signal.
Status: ✅ Actionable — reuses the existing @datadog/browser-rum dependency; internal-only instrumentation.
Design reference: n/a — observability config, no UI.
What to build
Emit a RUM custom action mekari_session.event with { event, matched } from each handler in plugins/mekari-session.js, and a RUM error on server_down and on postMessage origin mismatch. No console.log.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| extend | plugins/mekari-session.js | Import datadogRum from @datadog/browser-rum; call datadogRum.addAction('mekari_session.event', {...}) in handlers and datadogRum.addError(...) on server_down / origin mismatch |
| extend | tests/plugins/mekari-session.spec.js | Assert addAction/addError called with the expected payload (mock datadogRum) |
File path rule:
plugins/datadog-rum.jsverified — itimport { datadogRum } from '@datadog/browser-rum'(line 1).@datadog/browser-rum@^4.34.0confirmed inpackage.json. Note:addAction/addErrorare not yet used anywhere in the repo — confirm the v4 API surface (datadogRum.addAction(name, context),datadogRum.addError(error, context)) when wiring.
Implementation steps
- Explore the codebase area — Open
plugins/datadog-rum.js(verified) for theimport { datadogRum }form and theservice: 'qontak-crm-frontend'convention (line 9). Confirm RUM is initialised before the session plugin runs (datadog-rum atnuxt.config.js:66, session plugin registered after — verify ordering). - Write failing tests (red) — Extend
tests/plugins/mekari-session.spec.js: mockdatadogRum, assertaddAction('mekari_session.event', { event, matched })fires per handled event andaddErrorfires onserver_down+ origin mismatch. Run, confirm red. - Scaffold — Add
import { datadogRum } from '@datadog/browser-rum'to the plugin. - Wire state — Pass
{ event: <name>, matched: <bool> }from each handler. - Implement behavior — Add
addActionto all four handlers;addErrorin theserver_downbranch and the origin-mismatch guard (Task 2). - Go green —
yarn test-file tests/plugins/mekari-session.spec.js. - Quality gate —
yarn lint:js && yarn build.
Acceptance criteria
- Each handled SDK event emits
datadogRum.addAction('mekari_session.event', { event, matched }). -
server_downandpostMessageorigin mismatch emitdatadogRum.addError(...). - No new
console.logintroduced.
Test strategy
Mock the @datadog/browser-rum module; spy on addAction/addError. Key assertion: action name + payload shape per event; error emitted on the two failure conditions.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 0.5 |
| Backend | — |
| QA | 0 |
| Total | 0.5 |
Assumptions: reuses existing
@datadog/browser-rumdep + RUM init; internal-only (QA 0); thin layer over Task 3's handlers.
Run to verify
yarn test-file tests/plugins/mekari-session.spec.js && yarn lint:js && yarn build
Depends on
- [Task 2] (origin guard to instrument) and [Task 3] (the handlers to instrument).
Task 6: [BE] Add @mekari/sdk dependency 🚫 (S1 dependency)
The
@mekari/sdkSessionpackage is installed and pinned so the plugin (Task 2/3) can import it for real and the build resolves it.
Status: 🚫 Blocked — unblock condition: confirm @mekari/sdk is published to a Mekari/private npm registry CRM can install from (RFC §5 Q6 [important]). The package is absent from package.json today (verified — only @mekari/pixel). Until then, Tasks 2/3/5 run against a stubbed/commented import.
Design reference: n/a — dependency change, no UI.
What to build
Run yarn add @mekari/sdk (pinned), commit package.json + yarn.lock, then uncomment the real import { Session } from '@mekari/sdk' in plugins/mekari-session.js (the factory seam left in Task 2).
Implementation Plan
| Action | File | What changes |
|---|---|---|
| extend | package.json (+ yarn.lock) | Add pinned @mekari/sdk dependency |
| extend | plugins/mekari-session.js | Uncomment/replace the stubbed import with the real @mekari/sdk Session |
File path rule:
package.jsonverified (no@mekari/sdkpresent).plugins/mekari-session.jsis created in Task 2.
Implementation steps
- Verify registry access (Q6) — Confirm CRM's
.npmrc/ registry can resolve@mekari/sdk. If it 404s, the task stays blocked — escalate to A&L. yarn add @mekari/sdk(pin exact version).- Replace the Task-2 import seam with
import { Session } from '@mekari/sdk'. yarn install --frozen-lockfilepasses;yarn buildresolves the import.
Acceptance criteria
-
@mekari/sdkpresent inpackage.jsondeps at a pinned version. -
yarn install --frozen-lockfilepasses. -
yarn buildresolves the@mekari/sdkimport.
Test strategy
No new unit test — covered by Tasks 2/3/5 once the real module replaces the mock; the gate is yarn build resolving the import.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 0.5 |
| Backend | — |
| QA | 0 |
| Total | 0.5 |
Assumptions: labelled
[BE]/infra-ish (dependency + registry), trivial once Q6 confirms availability; no code logic.
Run to verify
yarn install --frozen-lockfile && yarn build
Depends on
- [External:
@mekari/sdkregistry availability — Q6 (pending)]. Unblocks the real imports in Tasks 2, 3, 5.
Task 7: [FE] switch_user proper SSO-autologin flow 🚫 (S3 / Q1)
On
switch_user, instead of the interim "sign out + re-login", CRM seamlessly re-establishes a session for the new account via the SSO-autologin round-trip (PRD web-session flow) — no credential re-entry.
Status: 🚫 Blocked — unblock condition: resolve RFC §5 Q1 [critical]: CRM is token-based (not OAuth2 authz-code) and has no SSO-autologin callback route today (crm-user redirects to crmV1Host/login). The SSO-autologin contract for token products must be defined (by A&L) before this can be built without hallucinating the contract. Interim behaviour (sign-out + toast) ships in Task 3.
Design reference: n/a — design pending (depends on the resolved flow; possible bespoke interstitial is RFC §5 Q5).
What to build (once unblocked)
An SSO-autologin redirect/callback path: destroy product session → redirect to product sign-in → SSO autologin with valid _mekari_account → callback → new product session → fetch current company. Requires a CRM callback route + token re-issue path that does not exist today.
Implementation Plan
| Action | File | What changes |
|---|---|---|
| create | pages/... or middleware/... callback route | [unverified — contract undefined] SSO-autologin callback |
| extend | plugins/mekari-session.js | Replace interim switch_user handler with the autologin round-trip |
| extend | schemes/crmAuthScheme.js | [unverified] token re-issue from SSO callback |
File path rule: all paths
[unverified — check repo / contract undefined]. The exact route and token-reissue mechanism cannot be specified until Q1's contract exists.
Acceptance criteria
- (pending Q1)
switch_userre-establishes a session for the new account without credential re-entry, per the defined SSO-autologin contract. - Current company resolves for the new account after switch.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 3.0 |
| Backend | — |
| QA | 0.5 |
| Total | 3.5 |
Assumptions: coarse estimate — no contract exists (Q1). Likely also needs BE/SSO callback support not scoped here. Figure is a placeholder to size the deferred work, not a committed estimate.
Depends on
- [External: SSO-autologin contract for token products — Q1
[critical](pending)], [Task 2], [Task 3], [Task 6].
Task 8: [FE+BE] current_company sync after session 🚫 (S4)
After a session is (re-)established, CRM reflects the user's current SSO company so the product context matches SSO.
Status: 🚫 Blocked — unblock condition: resolve RFC §5 Q4 [critical] + ADR-7: no CRM current_company endpoint or field exists (only team endpoints: store/user.js:244,261,274), and whether CRM even has a per-company-switch equivalent is unconfirmed. Needs a BE RFC/contract before any FE work. Deferred entirely in this RFC.
Design reference: n/a — deferred.
What to build (once unblocked)
FE consumption of a (to-be-defined) CRM current_company endpoint/field to set company context after logged_in/switch_user. The endpoint itself is BE work (separate RFC).
Implementation Plan
| Action | File | What changes |
|---|---|---|
| create | BE current_company endpoint | [unverified — covered in BE RFC, link REQUIRED] |
| extend | store/user.js | [unverified] set company context from the new endpoint |
File path rule: all paths
[unverified — check repo / BE RFC pending]. CRM uses teams, not SSOcurrent_company— no contract to anchor against today.
Acceptance criteria
- (pending Q4 + BE RFC) CRM company context matches the SSO current company after session establishment.
Effort estimate
| Discipline | Days |
|---|---|
| Frontend | 1.0 |
| Backend | 2.0 |
| QA | 0.5 |
| Total | 3.5 |
Assumptions: coarse — BE endpoint with business logic (2–3 BE days range, taken at 2.0) plus FE wiring; no in-repo contract (Q4). Estimate is a placeholder for deferred scope.
Depends on
- [External: CRM
current_companyBE contract / BE RFC — Q4[critical](pending)], [Task 2], [Task 3].
Ordering rationale
- Start with Task 1 (msli helper). It is pure, fully actionable, and is the decision engine Task 3's
server_downhandler consumes — building it first lets the plugin handlers wire against a tested helper rather than a stub. Zero external dependency. - Task 2 then Task 3 form the plugin core. Task 2 (shell + toggle gate + origin guard) must precede Task 3 (event handlers) — same file, and Task 3 attaches handlers to the subscription seam Task 2 creates. Both are buildable now with the SDK import and
user_sso_idstubbed; this is the critical path and the bulk of the FE effort (4.5 FE days). - Task 4 (logout → SSO redirect) is independent and should land alongside Task 3, because Task 3's sign-out events (
logged_out/switch_user/server_down) reuse that redirect. It's safely toggle-gated, so it can merge before the SDK is even installed. - Task 5 (RUM) is a thin final layer over Tasks 2/3 — do it last among the actionable set; it's needed for the rollout's go/no-go signal but blocks nothing.
- Critical path to "done" runs through the external blockers, not CRM code. The four
[critical]OQs are the real gate (the RFC's own §7 marker is "no"). Push A&L/Infosec on: Q6 (publish@mekari/sdkto the registry → unblocks the real import in Task 6, turning Tasks 2/3/5 from stubbed to complete), Q2 (user_sso_idfield in/users/me→ unblocks theSessionconstructor arg), Q3 (exactcentralized_sessioncode string), and Q1/Q4 (the SSO-autologin andcurrent_companycontracts → unblock Tasks 7/8). Also confirm Q5 (toast component) before finishing Task 3 and Q8 (CSP delivery mechanism, Infosec) before pilot. Net: ~9 man-days of CRM FE work (Tasks 1–5) can proceed today behind the toggle, but cannot be fully wired/shipped until Q6 + Q2 + Q3 land.
Skipped stories
Full-scope mode — every 🚫 Blocked task with its unblock condition (detail is in each task body above):
| Story / Task | Reason / unblock condition |
|---|---|
Task 6 — Add @mekari/sdk dep (S1 dep) | Blocked on Q6 [important] — confirm @mekari/sdk is published to a Mekari/private npm registry CRM can install from. Package absent from package.json today. |
Task 7 — switch_user proper SSO-autologin flow (S3 / Q1) | Blocked on Q1 [critical] — CRM is token-based with no SSO-autologin callback route; the autologin contract for token products is undefined. Interim sign-out+toast ships in Task 3. |
Task 8 — current_company sync (S4) | Blocked on Q4 [critical] + ADR-7 — no CRM current_company endpoint/field exists (CRM uses teams); needs a BE RFC/contract first. Deferred. |
Additional non-blocking unknowns that constrain the actionable tasks (not skipped, but flagged in-task): Q2 (
user_sso_idsource — stubs Task 2's constructor arg), Q3 (exactcentralized_sessioncode — stubs Task 2's gate), Q5 (toast component — Task 3), Q7 (logout-caller regression audit — Task 4), Q8 (CSP delivery, Infosec — pilot gate).