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 atnuxt.config.js:84. @mekari/sdkis not inpackage.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:610emitsuser-sign-out;SwitchAccount.vue:299/302$on/$off;:376doSignOut;:409SSOsign_out;hubAuthScheme.js:36logout;store/organization.js:30feature_flag, getters at:41;middleware/login.js:22auth-code redirect;utils/general.js:104storage helper).
Effort Summary
| Task | Story / Chunk | Status | FE days | BE days | QA days | Total |
|---|---|---|---|---|---|---|
| 1 — Toggle plumbing + SDK boot plugin + mixin scaffold | Chunk 1–2 (toggle, load SDK) | ⚠️ Partially blocked (OQ-3) | 2 | — | 0.5 | 2.5 |
2 — Wire mixin into boot + logged_out + logged_in/msli | Chunk 3 | ✅ Actionable | 1.5 | — | 0.5 | 2 |
3 — switch_user re-auth | Chunk 4 | ✅ Actionable | 1.5 | — | 0.5 | 2 |
4 — server_down fallback + msli heuristic | Chunk 5 | ✅ Actionable | 1.5 | — | 0.5 | 2 |
| 5 — Observability (RUM + Mixpanel) | Chunk 7 | ✅ Actionable | 0.5 | — | 0 | 0.5 |
6 — logged_in company sync | Chunk 6 / story "logged_in + company sync" | 🚫 Blocked (OQ-1) | 1 | 2 | 0.5 | 3.5 |
| Grand total | 8 | 2 | 2.5 | 12.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/sdkevent contract is unsettled — the RFC itself flagsdata.status === 'logout'vslogged_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-3users/me/current_companycontract, not a confirmed spec. The toggle is also inert until OQ-2 ships thecentralized_sessionorg-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_sessionflag ON has themekari-sessionSDK constructed once per authed shell with theirsso_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
| Action | File | What changes |
|---|---|---|
| create | plugins/mekari-session.js | Nuxt 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). |
| create | assets/mixins/session/centralizedSession.js | Mixin: 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. |
| create | assets/mixins/session/__test__/centralizedSession.spec.js | Tests: 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. |
| modify | nuxt.config.js (plugins array :84) | Register '@/plugins/mekari-session.js'. |
| modify | nuxt.config.js (env block :250) | Add MEKARI_SESSION_SDK_URL env key. |
| modify | package.json | Add @mekari/sdk dependency — deferred until OQ-3; until then the spec mocks the module via jest.mock('@mekari/sdk'). |
| read | store/organization.js:30,41 | Confirm 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
- Explore — Open
plugins/hotjar.js(3-line third-party boot pattern) andassets/mixins/metric/mixpanelMixin.js+ its test atassets/mixins/metric/__test__/mixpanelMixin.spec.jsto copy the mixin +__test__/spec layout and@/alias style. Openstore/organization.js:41to see the getter shape. - Write failing tests (red) — Create
assets/mixins/session/__test__/centralizedSession.spec.js.jest.mock('@mekari/sdk', () => ({ Session: jest.fn() })). Assert: flag-ON +sso_id⇒Sessioncalled once with{ current_user: <sso_id> }; flag-OFF ⇒Sessionnot called; nosso_id⇒ not called. Runnpm run test -- --testPathPattern="centralizedSession"and confirm red. - Scaffold plugin — Create
plugins/mekari-session.jsmodelled onplugins/hotjar.js; register'@/plugins/mekari-session.js'in thenuxt.config.js:84plugins array and addMEKARI_SESSION_SDK_URLto the env block at:250. - Scaffold mixin — Create
assets/mixins/session/centralizedSession.jswithcomputed.isCentralizedSessionEnabledreading the orgfeature_flaggetter, aninitSession()method, and an emptyhandleSessionEvent(data, error)(handlers added in Tasks 2–4), plusbeforeDestroyteardown. - Wire state — In
initSession(), guard onisCentralizedSessionEnabled && this.$auth?.user?.sso_id; inside the guardconst { Session } = await import('@mekari/sdk');this._session = new Session({ current_user: this.$auth.user.sso_id });this._session.on('event', this.handleSessionEvent). - Go green —
npm run test -- --testPathPattern="centralizedSession"until all pass. - Quality gate —
npm run lint && npm run build(build will fail to resolve@mekari/sdkonly 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 dynamicimport()inside the guard so tree-shaking + jest mock both hold).
Acceptance criteria
- Toggle ON +
sso_idpresent ⇒Sessionconstructed exactly once withcurrent_user = sso_id. - Toggle OFF ⇒
Sessionnever constructed, no iframe (mock asserts 0 calls). -
sso_idabsent ($auth not ready) ⇒ SDK init skipped (Branch & Skip Catalog §2.9). -
beforeDestroyremoves the SDK event listener (AGENTS.md leak rule). - (pending OQ-3) real
@mekari/sdkadded topackage.jsonand 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
| Discipline | Days |
|---|---|
| Frontend | 2 |
| Backend | — |
| QA | 0.5 |
| Total | 2.5 |
Assumptions: reuses the existing org
feature_flaggetter (no store change); SDK mocked in tests so OQ-3 doesn't block this task's dev/test;@mekari/pixelis already a dependency (no new tooling). Newassets/mixins/session/dir follows existing sibling-dir convention.
Run to verify
npm run test -- --testPathPattern="centralizedSession" && npm run lint
Depends on
- [External:
@mekari/sdkpublished 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
mslitimestamp so theserver_downfallback (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
| Action | File | What changes |
|---|---|---|
| modify | components/layouts/main/InitComponent.vue | Import + register the centralizedSession mixin; call initSession() after $auth.loggedIn is confirmed (alongside existing boot side-effects ~:1138 where sso_id is already read). |
| extend | assets/mixins/session/centralizedSession.js | In 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. |
| extend | utils/general.js | Add writeMsli() / readMsli() / removeMsli() helpers beside storeUserLoggedInLocalStorage (:104); export from the existing export block (:333). |
| extend | assets/mixins/session/__test__/centralizedSession.spec.js | Tests: logged_out event ⇒ EventBus.$emit called once with 'user-sign-out' + msli removed; logged_in event ⇒ localStorage.msli is a numeric timestamp. |
| extend | utils/__test__/general.spec.js | Tests for the new msli helpers (write sets numeric, remove clears). |
All paths repo-verified.
Implementation steps
- Explore — Open
components/layouts/main/InitComponent.vue:609-610(the existingEventBus.$emit('user-sign-out')relay) andSwitchAccount.vue:299-302,376to confirm the relay→doSignOutchain you're reusing. Openutils/general.js:104for the storage-helper pattern andutils/__test__/general.spec.jsfor its test style. - Write failing tests (red) — Extend
centralizedSession.spec.js: dispatch a fake{ status: 'logged_out' }⇒ assertEventBus.$emitcalled with'user-sign-out'; dispatch{ status: 'logged_in' }⇒ assertlocalStorage.getItem('msli')parses to a number. Addgeneral.spec.jscases for the helpers. Runnpm run test -- --testPathPattern="centralizedSession|general"→ red. - Scaffold helpers — Add
writeMsli/readMsli/removeMslitoutils/general.jsand export them at:333. - Wire state — In the mixin import
{ EventBus }from@/plugins/event-busand the msli helpers from@/utils/general. - Implement behavior — Fill
handleSessionEventlogged_outandlogged_inbranches; register the mixin inInitComponent.vueand invokeinitSession()after$auth.loggedIn. - Go green —
npm run test -- --testPathPattern="centralizedSession|general"until green. - Quality gate —
npm run lint && npm run build.
Acceptance criteria
-
logged_outevent ⇒EventBus.$emit('user-sign-out')invoked exactly once,msliremoved. -
logged_inevent ⇒localStorage.msliset to a numericDate.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
| Discipline | Days |
|---|---|
| Frontend | 1.5 |
| Backend | — |
| QA | 0.5 |
| Total | 2 |
Assumptions: reuses the verified
user-sign-outEventBus relay anddoSignOut()(no new sign-out code);mslihelpers are thin wrappers overlocalStoragemodelled onutils/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
| Action | File | What changes |
|---|---|---|
| extend | assets/mixins/session/centralizedSession.js | case 'switch_user': this.removeMsli() → await this.$auth.logout({ token }) → redirect to the auth-code URL → emit toast. |
| extend | middleware/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. |
| extend | assets/mixins/session/__test__/centralizedSession.spec.js | Tests: 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. |
| read | middleware/sso-callback.js:130 | doSSOValidation() 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
- Explore — Open
middleware/login.js:15-22to copy the exact auth-code redirect URL,schemes/hubAuthScheme.js:36-48for thelogout(params)signature, andmiddleware/sso-callback.js:130to confirm the callback re-validates. Grep for an existing toast helper (MpNotification/$notify) and pick the one Hub TL confirms. - Write failing tests (red) — Extend the spec: dispatch
{ status: 'switch_user', current_user: 'other-sso' }; mockwindow.location; assert$auth.logoutcalled, location containsresponse_type=code+sso-callback,msliremoved. Runnpm run test -- --testPathPattern="centralizedSession"→ red. - Scaffold — If extracting, add an exported
buildSsoAuthCodeUrl()tomiddleware/login.js; otherwise add the equivalent builder to the mixin. - Wire state — Import
$auth(viathis.$auth) and the URL builder. - Implement behavior — Fill the
switch_userbranch 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". - Go green —
npm run test -- --testPathPattern="centralizedSession"until green. - Quality gate —
npm run lint && npm run build.
Acceptance criteria
-
switch_userevent ⇒$auth.logoutinvoked (token revoke + cookie clear). - Then
window.locationredirected to SSO/auth?response_type=code...redirect_uri=.../sso-callback. -
msliremoved before redirect. - "Your account has changed" toast shown (helper TBD — see Design reference).
- No new wildcard
postMessagelistener added (§3.2 — keep separate fromsso-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
| Discipline | Days |
|---|---|
| Frontend | 1.5 |
| Backend | — |
| QA | 0.5 |
| Total | 2 |
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] (
mslihelpers + event dispatch)
Task 4: [FE] Handle server_down — msli 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)mslitimestamp 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
| Action | File | What changes |
|---|---|---|
| extend | assets/mixins/session/centralizedSession.js | case 'server_down': read msli, compute freshness vs a 2h constant, check token validity, branch to stay vs EventBus.$emit('user-sign-out'). |
| extend | utils/general.js | (if needed) isMsliFresh(maxAgeMs) helper beside the msli helpers added in Task 2. |
| read | schemes/hubAuthScheme.js:152 | Token cookie key qontak._token.hub — read for validity check (presence/expiry). |
| extend | assets/mixins/session/__test__/centralizedSession.spec.js | Tests 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
- Explore — Open
schemes/hubAuthScheme.js:152to see howqontak._token.hubis set/read, and re-open themslihelpers added toutils/general.jsin Task 2. - Write failing tests (red) — Extend the spec with two cases (fresh+valid ⇒ stay; stale/invalid ⇒ sign-out), mocking
localStorageand the token cookie. Runnpm run test -- --testPathPattern="centralizedSession"→ red. - Scaffold — Add an
isMsliFresh()helper if the inline check is non-trivial; define aTWO_HOURS_MSconstant. - Wire state — Import the msli helpers and a token-read util.
- Implement behavior — Fill the
server_downbranch with the ADR-6 three-condition guard. - Go green —
npm run test -- --testPathPattern="centralizedSession"until green. - Quality gate —
npm run lint && npm run build.
Acceptance criteria
-
server_down+now - msli < 2h+ validqontak._token.hub⇒ stays logged in, nouser-sign-out. -
server_down+ stalemsli(≥2h) OR missing/invalid token ⇒EventBus.$emit('user-sign-out')once. - No
mslipresent ⇒ 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
| Discipline | Days |
|---|---|
| Frontend | 1.5 |
| Backend | — |
| QA | 0.5 |
| Total | 2 |
Assumptions: reuses Task 2
mslihelpers + 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] (
mslihelpers)
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_inRUM action appears) and can alert on aserver_downspike 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
| Action | File | What changes |
|---|---|---|
| extend | assets/mixins/session/centralizedSession.js | In 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. |
| read | plugins/datadog-rum.ts | Confirm the RUM client accessor (global vs injected) to call addAction. |
| read | assets/mixins/metric/mixpanelMixin.js | Reuse the existing track method signature. |
| extend | assets/mixins/session/__test__/centralizedSession.spec.js | Test: each event ⇒ RUM addAction called with centralized_session.<event>; switch_user/sign-out ⇒ Mixpanel track called. |
All paths repo-verified.
Implementation steps
- Explore — Open
plugins/datadog-rum.tsfor theaddActionaccessor andassets/mixins/metric/mixpanelMixin.js(+ its__test__/mixpanelMixin.spec.js) for the track signature and how it's mocked in tests. - Write failing tests (red) — Extend the spec: spy on the RUM
addActionand the Mixpanel track; dispatch each event; assert the action name string. Runnpm run test -- --testPathPattern="centralizedSession"→ red. - Implement behavior — Add the telemetry calls inside each
handleSessionEventbranch (and theserver_downsign-out path). - Go green —
npm run test -- --testPathPattern="centralizedSession"until green. - Quality gate —
npm run lint && npm run build.
Acceptance criteria
- Each handled event emits a RUM action named
centralized_session.<event>(verifiable via mocked tracker). -
switch_userand 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
| Discipline | Days |
|---|---|
| Frontend | 0.5 |
| Backend | — |
| QA | 0 |
| Total | 0.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
| Action | File | What 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. |
| modify | common/constants/endpoint.js | Register the user.currentCompany key under the appropriate version block (file shape verified: vN.user.<key> map). |
| extend | store/organization.js (or a store/users action) | Add a fetchCurrentCompany action that calls the new endpoint key and commits the company. |
| extend | assets/mixins/session/centralizedSession.js | In the logged_in branch (and post-switch_user), await this.$store.dispatch('organization/fetchCurrentCompany'). |
| extend | assets/mixins/session/__test__/centralizedSession.spec.js | Test (FE): on logged_in, the sync action is dispatched once (action itself mocked). |
endpoint.jsandstore/organization.jspaths repo-verified. The BE handler path is[unverified — Hub BE repo]— not part of thelocal/hubFE 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.)
- Explore — Open
common/constants/endpoint.js(versionedusermap) for the key registration pattern, andstore/organization.jsfor the action/mutation style. - (BE, blocked) Build + document the
current_companyproxy per the OQ-1 contract; confirm method, path, and response shape. - Write failing tests (red) — Extend the spec:
logged_in⇒$store.dispatchcalled with the sync action (action mocked). Runnpm run test -- --testPathPattern="centralizedSession"→ red. - Implement (FE) — Register the endpoint key; add
fetchCurrentCompany; dispatch it from thelogged_inbranch. - Go green —
npm run test -- --testPathPattern="centralizedSession"until green. - Quality gate —
npm run lint && npm run build.
Acceptance criteria
- (pending OQ-1) Hub BE
GET current_companyendpoint exists with a confirmed contract. - On
logged_in, the company-sync action is dispatched exactly once. - After
switch_userre-auth, the company is re-synced. - (pending OQ-2) the path only runs when
centralized_sessionis 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
| Discipline | Days |
|---|---|
| Frontend | 1 |
| Backend | 2 |
| QA | 0.5 |
| Total | 3.5 |
Assumptions: BE side estimated as an endpoint-with-business-logic proxy against the unconfirmed ADR-3
users/me/current_companycontract — 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_inbranch) - [External: Hub BE
current_companyendpoint contract — OQ-1 (blocking)] - [External:
centralized_sessionorg-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/sdkso the unpublished package (OQ-3) does not stall coding. Tasks 2–5 are pure extensions ofhandleSessionEventon the same two files, so they merge naturally into one developer's flow (vertical merging rule applied — every task touchescentralizedSession.js+ its single spec). - Tasks 2 → 3 → 4 are ordered by primitive reuse:
logged_out/msli(Task 2) establishes themsliwrite + sign-out relay thatserver_down(Task 4) depends on;switch_user(Task 3) is independent of Task 4 but shares themsliremoval 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 thecurrent_companyproxy (OQ-1) and add thecentralized_sessionorg 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 / Task | Reason / 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 UX | Explicitly out of scope in the source RFC; no Hub FE change required. |