Skip to main content

Task Breakdown — Centralized Web Session (Hub FE Integration with mekari-session SDK)

Source RFC: centralized-web-session.md · Mode: Vertical (1 task = 1 chunk/story end-to-end: UI + API + tests) · Scope: full picture (blocked tasks shown inline) · Target repo: local/hub (verified at /Users/mekari/Documents/hub)

Reconnaissance notes (verified against the repo):

  • Test command: npm run test (jest --coverage); single-pattern run: npm run test -- --testPathPattern="<name>".
  • Lint: npm run lint · Build: npm run build (nuxt build).
  • Test files live in a __test__/ (singular) subfolder beside the source, named *.spec.js — e.g. assets/mixins/metric/__test__/mixpanelMixin.spec.js. (The RFC's §4.C wrote __tests__/ plural — corrected here to match the repo convention.)
  • Import alias is @/ (e.g. import { EventBus } from '@/plugins/event-bus').
  • Nuxt plugins are registered in the plugins: array at nuxt.config.js:84.
  • @mekari/sdk is not in package.json (only @mekari/pixel@^1.1.14) — confirms OQ-3.
  • assets/mixins/session/ does not exist yet (sibling domain dirs do: contact/, mqtt/, metric/, …) — new dir is consistent with convention.
  • All RFC-cited files and line anchors confirmed (InitComponent.vue:610 emits user-sign-out; SwitchAccount.vue:299/302 $on/$off; :376 doSignOut; :409 SSO sign_out; hubAuthScheme.js:36 logout; store/organization.js:30 feature_flag, getters at :41; middleware/login.js:22 auth-code redirect; utils/general.js:104 storage helper).

Effort Summary

TaskStory / ChunkStatusFE daysBE daysQA daysTotal
1 — Toggle plumbing + SDK boot plugin + mixin scaffoldChunk 1–2 (toggle, load SDK)⚠️ Partially blocked (OQ-3)20.52.5
2 — Wire mixin into boot + logged_out + logged_in/msliChunk 3✅ Actionable1.50.52
3 — switch_user re-authChunk 4✅ Actionable1.50.52
4 — server_down fallback + msli heuristicChunk 5✅ Actionable1.50.52
5 — Observability (RUM + Mixpanel)Chunk 7✅ Actionable0.500.5
6 — logged_in company syncChunk 6 / story "logged_in + company sync"🚫 Blocked (OQ-1)120.53.5
Grand total822.512.5

Confidence: medium. Chunks 1–5 are well-grounded (every auth primitive being reused already exists and is verified in-repo). The two biggest movers: (a) the @mekari/sdk event contract is unsettled — the RFC itself flags data.status === 'logout' vs logged_out (OQ-4), so event-handler wiring carries rework risk until A&L confirms canonical names/payload; (b) Task 6 (company sync) is fully blocked on an unbuilt Hub BE endpoint (OQ-1), and its BE side is estimated from the ADR-3 users/me/current_company contract, not a confirmed spec. The toggle is also inert until OQ-2 ships the centralized_session org-payload flag, but that does not block coding/testing behind a mocked flag.


Task 1: [FE] Toggle plumbing + SDK boot plugin + centralized-session mixin scaffold (Chunk 1–2 · stories "Load SDK with user_sso_id", "Gate behind centralized_session toggle")

A user on a company with the centralized_session flag ON has the mekari-session SDK constructed once per authed shell with their sso_id; with the flag OFF, no SDK and no iframe — today's behaviour byte-for-byte.

Status: ⚠️ Partially blocked — the real @mekari/sdk package is not yet published to Hub's registry (OQ-3), and the canonical event names/payload are unsettled (OQ-4). Actionable now: build the toggle getter, the boot plugin, the mixin lifecycle (construct/destroy), and all tests against a mocked @mekari/sdk. Swap the mock for the real dependency in package.json once OQ-3 closes.

Design reference: n/a — no visible UI (SDK injects a hidden iframe; §1.5 "SDK injects iframe; no visible UI"). Toast UI is introduced in Task 3.

What to build

A centralizedSession mixin in a new assets/mixins/session/ folder that, guarded by organization.feature_flag.centralized_session and the presence of $auth.user.sso_id, dynamically imports @mekari/sdk and constructs new Session({ current_user: sso_id }), subscribing to the event channel and tearing the listener down in beforeDestroy. A thin Nuxt plugin registers any global SDK config; the dynamic import() lives inside the toggle guard (ADR-2) so the bundle stays lazy.

Implementation Plan

ActionFileWhat changes
createplugins/mekari-session.jsNuxt plugin stub mirroring plugins/hotjar.js; reads MEKARI_SESSION_SDK_URL env for CSP allow-listing (load path stays the npm import per ADR-2).
createassets/mixins/session/centralizedSession.jsMixin: isCentralizedSessionEnabled computed (reads org getter), initSession() (toggle + sso_id guard → dynamic import('@mekari/sdk')new Session({ current_user })session.on('event', handler)), beforeDestroy teardown. Handlers stubbed in Task 2–4.
createassets/mixins/session/__test__/centralizedSession.spec.jsTests: toggle ON + sso_id present ⇒ Session constructed once with current_user = sso_id; toggle OFF ⇒ Session never constructed (mock asserts 0 calls); sso_id absent ⇒ not constructed.
modifynuxt.config.js (plugins array :84)Register '@/plugins/mekari-session.js'.
modifynuxt.config.js (env block :250)Add MEKARI_SESSION_SDK_URL env key.
modifypackage.jsonAdd @mekari/sdk dependency — deferred until OQ-3; until then the spec mocks the module via jest.mock('@mekari/sdk').
readstore/organization.js:30,41Confirm feature_flag getter exposes the map; centralized_session resolves to undefined→falsy (safe OFF) until OQ-2. No code change unless the getter isn't generic.

File path rule: all paths above are repo-verified except @mekari/sdk (the package itself — [unverified — pending OQ-3 publish]) and the toast/notification helper deferred to Task 3.

Implementation steps

  1. Explore — Open plugins/hotjar.js (3-line third-party boot pattern) and assets/mixins/metric/mixpanelMixin.js + its test at assets/mixins/metric/__test__/mixpanelMixin.spec.js to copy the mixin + __test__/ spec layout and @/ alias style. Open store/organization.js:41 to see the getter shape.
  2. Write failing tests (red) — Create assets/mixins/session/__test__/centralizedSession.spec.js. jest.mock('@mekari/sdk', () => ({ Session: jest.fn() })). Assert: flag-ON + sso_idSession called once with { current_user: <sso_id> }; flag-OFF ⇒ Session not called; no sso_id ⇒ not called. Run npm run test -- --testPathPattern="centralizedSession" and confirm red.
  3. Scaffold plugin — Create plugins/mekari-session.js modelled on plugins/hotjar.js; register '@/plugins/mekari-session.js' in the nuxt.config.js:84 plugins array and add MEKARI_SESSION_SDK_URL to the env block at :250.
  4. Scaffold mixin — Create assets/mixins/session/centralizedSession.js with computed.isCentralizedSessionEnabled reading the org feature_flag getter, an initSession() method, and an empty handleSessionEvent(data, error) (handlers added in Tasks 2–4), plus beforeDestroy teardown.
  5. Wire state — In initSession(), guard on isCentralizedSessionEnabled && this.$auth?.user?.sso_id; inside the guard const { Session } = await import('@mekari/sdk'); this._session = new Session({ current_user: this.$auth.user.sso_id }); this._session.on('event', this.handleSessionEvent).
  6. Go greennpm run test -- --testPathPattern="centralizedSession" until all pass.
  7. Quality gatenpm run lint && npm run build (build will fail to resolve @mekari/sdk only once the real import is un-mocked; until OQ-3 keep the import behind the mock in tests and do not add the bare import to runtime — gate on the dynamic import() inside the guard so tree-shaking + jest mock both hold).

Acceptance criteria

  • Toggle ON + sso_id present ⇒ Session constructed exactly once with current_user = sso_id.
  • Toggle OFF ⇒ Session never constructed, no iframe (mock asserts 0 calls).
  • sso_id absent ($auth not ready) ⇒ SDK init skipped (Branch & Skip Catalog §2.9).
  • beforeDestroy removes the SDK event listener (AGENTS.md leak rule).
  • (pending OQ-3) real @mekari/sdk added to package.json and dynamic import resolves at build.
  • (pending OQ-4) event channel name + handler signature match A&L's canonical contract.

Test strategy

Jest unit test on the mixin in isolation. Mock @mekari/sdk so Session is a jest.fn(); mount the mixin with a fake $auth and a stubbed Vuex getter for feature_flag. Key assertion: expect(Session).toHaveBeenCalledWith({ current_user: 'sso-123' }) under flag-ON, and expect(Session).not.toHaveBeenCalled() under flag-OFF.

Effort estimate

DisciplineDays
Frontend2
Backend
QA0.5
Total2.5

Assumptions: reuses the existing org feature_flag getter (no store change); SDK mocked in tests so OQ-3 doesn't block this task's dev/test; @mekari/pixel is already a dependency (no new tooling). New assets/mixins/session/ dir follows existing sibling-dir convention.

Run to verify

npm run test -- --testPathPattern="centralizedSession" && npm run lint

Depends on

  • [External: @mekari/sdk published to Hub registry — OQ-3 (pending; mock until then)]
  • [External: canonical SDK event names/payload — OQ-4 (pending; affects handler wiring in Tasks 2–4)]

Task 2: [FE] Wire mixin into boot + handle logged_out and logged_in/msli (Chunk 3 · stories "Handle logged_out", "msli write on confirmed login")

When SSO reports the user is logged out (or >2h idle), Hub signs the user out automatically; when SSO confirms the user is logged in, Hub records a fresh msli timestamp so the server_down fallback (Task 4) can trust a recent login.

Status: ✅ Actionable — both handlers map onto primitives that already exist and are verified (EventBus.$emit('user-sign-out')doSignOut(); a localStorage write modelled on utils/general.js:104).

Design reference: n/a — no visible UI (behavioural; sign-out reuses the existing doSignOut redirect).

What to build

Use the centralizedSession mixin inside InitComponent.vue (after $auth.loggedIn), and implement two of the four event branches: on logged_out, remove msli and emit EventBus.$emit('user-sign-out') (the relay SwitchAccount.vue:299 already turns into doSignOut()); on logged_in, write localStorage.msli = Date.now().

Implementation Plan

ActionFileWhat changes
modifycomponents/layouts/main/InitComponent.vueImport + register the centralizedSession mixin; call initSession() after $auth.loggedIn is confirmed (alongside existing boot side-effects ~:1138 where sso_id is already read).
extendassets/mixins/session/centralizedSession.jsIn handleSessionEvent: case 'logged_out'this.removeMsli() + EventBus.$emit('user-sign-out'); case 'logged_in'this.writeMsli() (localStorage.setItem('msli', Date.now())). Import EventBus from @/plugins/event-bus.
extendutils/general.jsAdd writeMsli() / readMsli() / removeMsli() helpers beside storeUserLoggedInLocalStorage (:104); export from the existing export block (:333).
extendassets/mixins/session/__test__/centralizedSession.spec.jsTests: logged_out event ⇒ EventBus.$emit called once with 'user-sign-out' + msli removed; logged_in event ⇒ localStorage.msli is a numeric timestamp.
extendutils/__test__/general.spec.jsTests for the new msli helpers (write sets numeric, remove clears).

All paths repo-verified.

Implementation steps

  1. Explore — Open components/layouts/main/InitComponent.vue:609-610 (the existing EventBus.$emit('user-sign-out') relay) and SwitchAccount.vue:299-302,376 to confirm the relay→doSignOut chain you're reusing. Open utils/general.js:104 for the storage-helper pattern and utils/__test__/general.spec.js for its test style.
  2. Write failing tests (red) — Extend centralizedSession.spec.js: dispatch a fake { status: 'logged_out' } ⇒ assert EventBus.$emit called with 'user-sign-out'; dispatch { status: 'logged_in' } ⇒ assert localStorage.getItem('msli') parses to a number. Add general.spec.js cases for the helpers. Run npm run test -- --testPathPattern="centralizedSession|general" → red.
  3. Scaffold helpers — Add writeMsli/readMsli/removeMsli to utils/general.js and export them at :333.
  4. Wire state — In the mixin import { EventBus } from @/plugins/event-bus and the msli helpers from @/utils/general.
  5. Implement behavior — Fill handleSessionEvent logged_out and logged_in branches; register the mixin in InitComponent.vue and invoke initSession() after $auth.loggedIn.
  6. Go greennpm run test -- --testPathPattern="centralizedSession|general" until green.
  7. Quality gatenpm run lint && npm run build.

Acceptance criteria

  • logged_out event ⇒ EventBus.$emit('user-sign-out') invoked exactly once, msli removed.
  • logged_in event ⇒ localStorage.msli set to a numeric Date.now() value.
  • Mixin is active only inside the authed shell (after $auth.loggedIn).
  • No direct doSignOut() re-implementation — only the EventBus relay is used (ADR-5).

Test strategy

Jest. Mock the SDK's event emitter to push { status } payloads into handleSessionEvent; spy on EventBus.$emit and on localStorage. Key assertion: expect(EventBus.$emit).toHaveBeenCalledWith('user-sign-out') for logged_out, and expect(Number(localStorage.getItem('msli'))).toBeGreaterThan(0) for logged_in.

Effort estimate

DisciplineDays
Frontend1.5
Backend
QA0.5
Total2

Assumptions: reuses the verified user-sign-out EventBus relay and doSignOut() (no new sign-out code); msli helpers are thin wrappers over localStorage modelled on utils/general.js:104.

Run to verify

npm run test -- --testPathPattern="centralizedSession|general" && npm run lint

Depends on

  • [Task 1] (mixin + boot plugin scaffold must exist)

Task 3: [FE] Handle switch_user — re-auth via authorization code + "account changed" toast (Chunk 4 · story "Handle switch_user")

When SSO reports a different user is now signed in, Hub revokes the stale session, re-authenticates the new user through the existing auth-code flow, and shows an "Your account has changed" notification — so the user never sees stale company data after switching accounts on SSO.

Status: ✅ Actionable — $auth.logout (hubAuthScheme.js:36) and the auth-code redirect (middleware/login.js:22) both exist and are verified. The only soft unknown is the exact toast helper.

Design reference: n/a — design pending — the "user has changed" notification is named in §1.5 as "Pixel toast / mp-broadcast" but no Figma frame is in the RFC. Notification helper is [unverified — confirm Hub's toast/notification API] (repo has mixed MpNotification/broadcast usage; no single convention). Confirm with Hub TL before wiring.

What to build

A switch_user branch in handleSessionEvent: remove msli, call $auth.logout({ token }) to revoke + clear cookies, then window.location redirect to the SSO /auth?response_type=code&...&redirect_uri=.../sso-callback URL (reusing middleware/login.js's builder), and surface a toast on return.

Implementation Plan

ActionFileWhat changes
extendassets/mixins/session/centralizedSession.jscase 'switch_user': this.removeMsli()await this.$auth.logout({ token }) → redirect to the auth-code URL → emit toast.
extendmiddleware/login.js(if needed) extract the /auth?response_type=code... URL builder (:22) into an exported helper so the mixin reuses it instead of duplicating the string. If extraction is risky, the mixin re-builds the same URL from the same SSO_URL/SSO_UNIFIED_CLIENT_ID env.
extendassets/mixins/session/__test__/centralizedSession.spec.jsTests: switch_user$auth.logout called, then window.location set to a URL containing response_type=code and redirect_uri=.../sso-callback; msli removed; toast emitted.
readmiddleware/sso-callback.js:130doSSOValidation() already handles the auth-code callback — no change; the redirect lands here on return.

Paths repo-verified except the toast helper (flagged above).

Implementation steps

  1. Explore — Open middleware/login.js:15-22 to copy the exact auth-code redirect URL, schemes/hubAuthScheme.js:36-48 for the logout(params) signature, and middleware/sso-callback.js:130 to confirm the callback re-validates. Grep for an existing toast helper (MpNotification/$notify) and pick the one Hub TL confirms.
  2. Write failing tests (red) — Extend the spec: dispatch { status: 'switch_user', current_user: 'other-sso' }; mock window.location; assert $auth.logout called, location contains response_type=code + sso-callback, msli removed. Run npm run test -- --testPathPattern="centralizedSession" → red.
  3. Scaffold — If extracting, add an exported buildSsoAuthCodeUrl() to middleware/login.js; otherwise add the equivalent builder to the mixin.
  4. Wire state — Import $auth (via this.$auth) and the URL builder.
  5. Implement behavior — Fill the switch_user branch in order: removeMsli()await this.$auth.logout({ token })window.location.assign(authCodeUrl); on callback return, fire the confirmed toast helper with "Your account has changed".
  6. Go greennpm run test -- --testPathPattern="centralizedSession" until green.
  7. Quality gatenpm run lint && npm run build.

Acceptance criteria

  • switch_user event ⇒ $auth.logout invoked (token revoke + cookie clear).
  • Then window.location redirected to SSO /auth?response_type=code...redirect_uri=.../sso-callback.
  • msli removed before redirect.
  • "Your account has changed" toast shown (helper TBD — see Design reference).
  • No new wildcard postMessage listener added (§3.2 — keep separate from sso-callback.js:23).

Test strategy

Jest with window.location mocked (Object.defineProperty). Key mock: $auth.logout = jest.fn().mockResolvedValue(). Key assertion: call order — $auth.logout resolves before window.location.assign is called with a URL matching /response_type=code.*sso-callback/.

Effort estimate

DisciplineDays
Frontend1.5
Backend
QA0.5
Total2

Assumptions: reuses verified $auth.logout + auth-code redirect (no new auth code); toast helper is a 1-liner once the API is confirmed. If toast helper needs design sign-off, that is a small QA/design follow-up, not added dev days here.

Run to verify

npm run test -- --testPathPattern="centralizedSession" && npm run lint

Depends on

  • [Task 1] (mixin scaffold)
  • [Task 2] (msli helpers + event dispatch)

Task 4: [FE] Handle server_downmsli heuristic fallback then sign-out (Chunk 5 · story "Handle server_down")

When the Session Manager is unreachable, Hub does not immediately log the user out — if there's a recent (<2h) msli timestamp and the local Hub token is still valid, the user stays logged in; otherwise Hub signs them out, keeping session behaviour consistent across Mekari without punishing a transient A&L outage.

Status: ✅ Actionable — msli helpers (Task 2) and the qontak._token.hub cookie validity check both exist and are verified (hubAuthScheme.js:152).

Design reference: n/a — no visible UI (silent fallback).

What to build

A server_down branch in handleSessionEvent implementing ADR-6: if readMsli() exists AND Date.now() - msli < 2h AND the qontak._token.hub cookie is valid ⇒ treat as logged_in (no sign-out); else EventBus.$emit('user-sign-out').

Implementation Plan

ActionFileWhat changes
extendassets/mixins/session/centralizedSession.jscase 'server_down': read msli, compute freshness vs a 2h constant, check token validity, branch to stay vs EventBus.$emit('user-sign-out').
extendutils/general.js(if needed) isMsliFresh(maxAgeMs) helper beside the msli helpers added in Task 2.
readschemes/hubAuthScheme.js:152Token cookie key qontak._token.hub — read for validity check (presence/expiry).
extendassets/mixins/session/__test__/centralizedSession.spec.jsTests both branches: now-msli<2h + valid token ⇒ no user-sign-out emitted; stale msli OR invalid token ⇒ user-sign-out emitted once.

All paths repo-verified.

Implementation steps

  1. Explore — Open schemes/hubAuthScheme.js:152 to see how qontak._token.hub is set/read, and re-open the msli helpers added to utils/general.js in Task 2.
  2. Write failing tests (red) — Extend the spec with two cases (fresh+valid ⇒ stay; stale/invalid ⇒ sign-out), mocking localStorage and the token cookie. Run npm run test -- --testPathPattern="centralizedSession" → red.
  3. Scaffold — Add an isMsliFresh() helper if the inline check is non-trivial; define a TWO_HOURS_MS constant.
  4. Wire state — Import the msli helpers and a token-read util.
  5. Implement behavior — Fill the server_down branch with the ADR-6 three-condition guard.
  6. Go greennpm run test -- --testPathPattern="centralizedSession" until green.
  7. Quality gatenpm run lint && npm run build.

Acceptance criteria

  • server_down + now - msli < 2h + valid qontak._token.hub ⇒ stays logged in, no user-sign-out.
  • server_down + stale msli (≥2h) OR missing/invalid token ⇒ EventBus.$emit('user-sign-out') once.
  • No msli present ⇒ treated as not-fresh ⇒ sign-out.

Test strategy

Jest. Mock localStorage.msli with Date.now() (fresh) and Date.now() - 3*3600*1000 (stale), and mock the token-cookie read true/false. Key assertion: EventBus.$emit is not called in the fresh+valid branch and is called once otherwise.

Effort estimate

DisciplineDays
Frontend1.5
Backend
QA0.5
Total2

Assumptions: reuses Task 2 msli helpers + the existing token cookie (no new persistence); the "2h" threshold is fixed per ADR-6.

Run to verify

npm run test -- --testPathPattern="centralizedSession" && npm run lint

Depends on

  • [Task 1] (mixin scaffold)
  • [Task 2] (msli helpers)

Task 5: [FE] Observability — RUM + Mixpanel per SDK event (Chunk 7)

Engineers and the on-call team can see, per piloted company, that centralized-session events are firing (a centralized_session.logged_in RUM action appears) and can alert on a server_down spike as a proxy for an A&L outage.

Status: ✅ Actionable — Datadog RUM (plugins/datadog-rum.ts) and the Mixpanel v2 mixin (assets/mixins/metric/mixpanelMixin.js) both exist and are verified.

Design reference: n/a — internal telemetry, no UI.

What to build

Emit one RUM custom action centralized_session.<event> per handled SDK event, and log switch_user + forced sign-outs to Mixpanel for funnel analysis (§3.3).

Implementation Plan

ActionFileWhat changes
extendassets/mixins/session/centralizedSession.jsIn handleSessionEvent, after each branch, emit datadogRum.addAction('centralized_session.' + status, {...}); for switch_user and sign-out paths also call the Mixpanel mixin's track method.
readplugins/datadog-rum.tsConfirm the RUM client accessor (global vs injected) to call addAction.
readassets/mixins/metric/mixpanelMixin.jsReuse the existing track method signature.
extendassets/mixins/session/__test__/centralizedSession.spec.jsTest: each event ⇒ RUM addAction called with centralized_session.<event>; switch_user/sign-out ⇒ Mixpanel track called.

All paths repo-verified.

Implementation steps

  1. Explore — Open plugins/datadog-rum.ts for the addAction accessor and assets/mixins/metric/mixpanelMixin.js (+ its __test__/mixpanelMixin.spec.js) for the track signature and how it's mocked in tests.
  2. Write failing tests (red) — Extend the spec: spy on the RUM addAction and the Mixpanel track; dispatch each event; assert the action name string. Run npm run test -- --testPathPattern="centralizedSession" → red.
  3. Implement behavior — Add the telemetry calls inside each handleSessionEvent branch (and the server_down sign-out path).
  4. Go greennpm run test -- --testPathPattern="centralizedSession" until green.
  5. Quality gatenpm run lint && npm run build.

Acceptance criteria

  • Each handled event emits a RUM action named centralized_session.<event> (verifiable via mocked tracker).
  • switch_user and forced sign-outs are tracked in Mixpanel.
  • No PII / token values are included in any telemetry payload (§3.2 A02).

Test strategy

Jest with the RUM client and the Mixpanel mixin method both mocked. Key assertion: expect(addAction).toHaveBeenCalledWith('centralized_session.logged_in', expect.any(Object)).

Effort estimate

DisciplineDays
Frontend0.5
Backend
QA0
Total0.5

Assumptions: pure instrumentation reusing existing RUM + Mixpanel clients; no user-facing behaviour ⇒ QA 0 (covered by unit tests).

Run to verify

npm run test -- --testPathPattern="centralizedSession" && npm run lint

Depends on

  • [Task 2], [Task 3], [Task 4] (the event branches to instrument)

Task 6: [FE+BE] logged_in company sync (Chunk 6 · story "Handle logged_in + company sync")

When SSO confirms login, Hub fetches and sets the user's current company so the user never sees the wrong company after an SSO-side account switch — fixing the second concrete bug in the source RFC.

Status: 🚫 Blocked — OQ-1 [critical]: Hub BE has no current-company endpoint (repo grep current_company|currentCompany → 0 hits, verified). The auth-code BE contract (users/me/current_company per ADR-3) must be built and confirmed by Hub BE before the FE wiring can be written against anything real. To unblock: Hub BE delivers and documents the GET current_company proxy endpoint and its response shape (OQ-1), and the org-payload centralized_session flag (OQ-2) so the path actually executes.

Design reference: n/a — no visible UI (company is set in state; surfaced through existing company UI).

What to build

BE (blocked): a Hub BE proxy endpoint GET current_company that calls SSO GET /v1.1/users/me/current_company (auth-code variant) and returns the company. FE (blocked on the BE contract): register the endpoint key in common/constants/endpoint.js, add a store action that calls it, and dispatch that action from the logged_in branch of handleSessionEvent (and after switch_user re-auth completes).

Implementation Plan

ActionFileWhat changes
create (BE)Hub BE service — current-company proxy handler[unverified — Hub BE repo, not present in local/hub FE repo] Proxy to SSO GET /v1.1/users/me/current_company; contract pending OQ-1.
modifycommon/constants/endpoint.jsRegister the user.currentCompany key under the appropriate version block (file shape verified: vN.user.<key> map).
extendstore/organization.js (or a store/users action)Add a fetchCurrentCompany action that calls the new endpoint key and commits the company.
extendassets/mixins/session/centralizedSession.jsIn the logged_in branch (and post-switch_user), await this.$store.dispatch('organization/fetchCurrentCompany').
extendassets/mixins/session/__test__/centralizedSession.spec.jsTest (FE): on logged_in, the sync action is dispatched once (action itself mocked).

endpoint.js and store/organization.js paths repo-verified. The BE handler path is [unverified — Hub BE repo] — not part of the local/hub FE checkout; contract itself is [pending OQ-1].

Implementation steps

(Do not start FE wiring until OQ-1 returns the confirmed endpoint contract and OQ-2 ships the flag.)

  1. Explore — Open common/constants/endpoint.js (versioned user map) for the key registration pattern, and store/organization.js for the action/mutation style.
  2. (BE, blocked) Build + document the current_company proxy per the OQ-1 contract; confirm method, path, and response shape.
  3. Write failing tests (red) — Extend the spec: logged_in$store.dispatch called with the sync action (action mocked). Run npm run test -- --testPathPattern="centralizedSession" → red.
  4. Implement (FE) — Register the endpoint key; add fetchCurrentCompany; dispatch it from the logged_in branch.
  5. Go greennpm run test -- --testPathPattern="centralizedSession" until green.
  6. Quality gatenpm run lint && npm run build.

Acceptance criteria

  • (pending OQ-1) Hub BE GET current_company endpoint exists with a confirmed contract.
  • On logged_in, the company-sync action is dispatched exactly once.
  • After switch_user re-auth, the company is re-synced.
  • (pending OQ-2) the path only runs when centralized_session is ON.

Test strategy

Jest (FE side only, once unblocked): mock the store action; assert $store.dispatch('organization/fetchCurrentCompany') is called on logged_in. The BE endpoint gets its own service-side test once the contract lands.

Effort estimate

DisciplineDays
Frontend1
Backend2
QA0.5
Total3.5

Assumptions: BE side estimated as an endpoint-with-business-logic proxy against the unconfirmed ADR-3 users/me/current_company contract — will move once OQ-1 lands the real spec; FE side reuses the existing endpoint-registry + store-action patterns.

Run to verify

npm run test -- --testPathPattern="centralizedSession" && npm run lint

Depends on

  • [Task 1], [Task 2] (mixin + event dispatch + logged_in branch)
  • [External: Hub BE current_company endpoint contract — OQ-1 (blocking)]
  • [External: centralized_session org-payload flag — OQ-2 (path inert until shipped)]

Ordering rationale

  • Critical path runs through Task 1. The mixin + boot plugin are the spine everything else hangs off; build it first against a mocked @mekari/sdk so the unpublished package (OQ-3) does not stall coding. Tasks 2–5 are pure extensions of handleSessionEvent on the same two files, so they merge naturally into one developer's flow (vertical merging rule applied — every task touches centralizedSession.js + its single spec).
  • Tasks 2 → 3 → 4 are ordered by primitive reuse: logged_out/msli (Task 2) establishes the msli write + sign-out relay that server_down (Task 4) depends on; switch_user (Task 3) is independent of Task 4 but shares the msli removal from Task 2, so it slots between them.
  • Task 5 (observability) comes last among the actionable set — it instruments branches that must already exist, so it can't precede Tasks 2–4, but it carries no behavioural risk and can ship in the same PR series.
  • Task 6 (company sync) is fully blocked and parked at the end — it needs a Hub BE endpoint that does not exist (OQ-1) and an org flag that does not exist (OQ-2). It is the one task that genuinely cannot start; the other five are executable today behind the (currently OFF) toggle, exactly as the RFC's §5 "Known limitation" states.
  • Push externally on three fronts in parallel with Task 1: A&L to publish @mekari/sdk + confirm canonical event names/payload (OQ-3, OQ-4); Hub BE to build the current_company proxy (OQ-1) and add the centralized_session org flag (OQ-2). OQ-4 in particular is a coding-risk multiplier across Tasks 2–4 — resolve it before the handler-wiring PR merges.

Skipped stories

(Full-scope mode: every 🚫 Blocked task listed with its unblocking condition. Partially-blocked Task 1 is in the main list, not here, since its actionable portion ships now.)

Story / TaskReason / unblock condition
Task 6 — logged_in company sync (story "Handle logged_in + company sync", Chunk 6)🚫 Blocked on OQ-1 [critical] — Hub BE has no current_company endpoint (repo grep: 0 hits); needs the confirmed auth-code users/me/current_company proxy contract. Also gated by OQ-2 (the centralized_session org flag) before the path executes.
(story "Wire logout to also hit SSO sign_out")Excluded — already implemented (SwitchAccount.vue:409 redirects to ${SSO_ACCOUNT_URL}/sign_out); RFC marks it n/a — already implemented, reused by Task 2's sign-out relay, no new work.
(Out of scope §1.3) @mekari/sdk package, Session Manager (Golang), dedicated Redis, SSO Kong account.mekari.com/sm/*Owned by Account & Launchpad — upstream dependencies, not built in this RFC.
(Out of scope §1.3) Auto-revoke of access/refresh tokens on inactivity; multiple-sessions-per-account UXExplicitly out of scope in the source RFC; no Hub FE change required.