Skip to main content

Task Breakdown — Centralized Web Session (Hub FE Integration with mekari-account-web-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 predominantly live in a __test__/ (singular) subfolder beside the source, named *.spec.js — e.g. assets/mixins/metric/__test__/mixpanelMixin.spec.js (37 such directories repo-wide). A pluralized __tests__/ convention also exists in some domains (mixins/utils/layouts/store) — both patterns are present in the repo; singular dominates and is used here, but __tests__ is not simply "wrong". (The RFC's §4.C wrote __tests__/ plural — this breakdown uses the dominant singular __test__/ 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-account-web-sdk is not in package.json (only @mekari/pixel@^1.1.14) — it is added as a git dependency per ADR-2, not a registry package, so there is no publish/access blocker (OQ-3 resolved).
  • 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).

Effort Summary

TaskStory / ChunkStatusFE daysBE daysQA daysTotal
1 — Toggle plumbing + SDK boot plugin + mixin scaffoldChunk 1–2 (toggle, load SDK)✅ Actionable (OQ-3 resolved — git dependency, no publish/registry blocker)20.52.5
2 — Wire mixin into boot + logged_outChunk 3✅ Actionable10.51.5
4 — server_down — fail-open, no actionChunk 5✅ Actionable0.50.51
5 — Observability (RUM + Mixpanel)Chunk 7✅ Actionable0.500.5
6 — logged_in company syncChunk 6 / story "logged_in + company sync"🚫 Blocked (OQ-1, OQ-2)100.51.5
Grand total (recomputed, latest review + BE grounding)5027

BE grounding (latest review, verified against hub-core + hub-service, the Chat backend). Task 6's BE was set to 0 here to remove a double-count: Hub (Chat v1) and Hub Chat v2 are two frontends of the same hub-core/hub-service backend, so the current-company sync endpoint is built once and its BE effort is carried in hub-chat-v2-fe.task-breakdown.md Task 7 (2.0 BE). Hub's FE still consumes it (FE 1.0 retained). Grounded facts: the SSO company plumbing already exists (hub-core/app/apps/mekari_sso/services/get_owned_companies.rb, get_company.rb, and login-time reconciliation interactors/oauths/user_get_token.rb:127-138); Chat has no per-session "current company" (a user is hard-bound 1:1 to one organization_id), so the shared BE work is "wire a new sync onto existing SSO services + the single-org model," not greenfield SSO integration. (Logout-chain flags seamless_auth_token/revoke_sso_token_on_logout and Interactors::Oauths::Revoke at revoke.rb:19-26 are confirmed present, unchanged by this pass.)

Task 3 removed (was: switch_user re-auth, ~2 FE + 0.5 QA d). The real SDK (mekari-account-web-sdk v0.3.0) has no switch_user status — an SSO account switch surfaces indistinguishably as logged_out and is handled by Task 2's existing sign-out path (R5). The task-number gap at 3 is intentional, not an error — it preserves traceability to the RFC's chunk numbers (hub-fe.md §4.C also keeps the gap at Chunk 4).

Grand total revised from 12.5 d to 9 d (latest review reconciliation): the deleted switch_user task (-2 FE/-0.5 QA), the simplified server_down no-op (Task 4: -1 FE/0 QA vs. the original msli-heuristic estimate), and the removed msli-write helper work in Task 2 (-0.5 FE) account for the reduction.

Confidence: medium-high. Tasks 1, 2, 4, 5 are well-grounded (every auth primitive being reused already exists and is verified in-repo), and the SDK contract itself is now fully resolved against the real mekari-account-web-sdk v0.3.0 source (three statuses, single-arg callback, no switch_user) — the single biggest historical rework risk (the logout/logged_out contradiction, OQ-4) is gone. 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-account-web-sdk Session constructed once per authed shell with their sso_id (as currentUser); with the flag OFF, no SDK and no iframe — today's behaviour byte-for-byte.

Status: ✅ Actionable. mekari-account-web-sdk is a git dependency, not a registry package — OQ-3 is resolved, so this task is not blocked waiting on a publish/registry grant. The canonical event contract is also resolved (OQ-4): three statuses, single-arg callback (wired in Task 2).

Design reference: n/a — no visible UI (SDK injects a hidden iframe; §1.5 "SDK injects iframe; no visible UI").

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-account-web-sdk and constructs new Session({ currentUser: sso_id }), subscribing to the single session.on('event', handleSessionEvent) channel and tearing the session down via session.destroy() (not .off() alone — the SDK is a singleton and destroy() is what resets it and removes the internal window listener, R11) in beforeDestroy. A thin Nuxt plugin handles registration; the dynamic import() lives inside the toggle guard (ADR-2) so the bundle stays lazy.

Implementation Plan

ActionFileWhat changes
createplugins/centralized-session.jsNuxt plugin stub mirroring plugins/hotjar.js's registration pattern. The SDK is a bundled git dependency (ADR-2) — no CDN/env plumbing to manage.
createassets/mixins/session/centralizedSession.jsMixin: isCentralizedSessionEnabled computed (reads org getter), initSession() (toggle + sso_id guard → dynamic import('mekari-account-web-sdk')new Session({ currentUser })session.on('event', handler)), beforeDestroy calls this._session?.destroy(). Handlers stubbed in Task 2.
createassets/mixins/session/__test__/centralizedSession.spec.jsTests: toggle ON + sso_id present ⇒ Session constructed once with currentUser = sso_id; toggle OFF ⇒ Session never constructed (mock asserts 0 calls); sso_id absent ⇒ not constructed.
modifynuxt.config.js (plugins array :84)Register '@/plugins/centralized-session.js'.
modifypackage.jsonAdd mekari-account-web-sdk as a git dependency pinned to a version tag (ADR-2). No mock required at merge time since there is no registry wait, but tests still use jest.mock('mekari-account-web-sdk') for isolation.
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-account-web-sdk itself, which is a new dependency to be added per ADR-2.

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-account-web-sdk', () => ({ Session: jest.fn() })). Assert: flag-ON + sso_idSession called once with { currentUser: <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/centralized-session.js modelled on plugins/hotjar.js; register '@/plugins/centralized-session.js' in the nuxt.config.js:84 plugins array.
  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) (single argument — handlers added in Task 2), plus beforeDestroy teardown calling this._session?.destroy().
  5. Wire state — In initSession(), guard on isCentralizedSessionEnabled && this.$auth?.user?.sso_id; inside the guard const { Session } = await import('mekari-account-web-sdk'); this._session = new Session({ currentUser: 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. Add mekari-account-web-sdk to package.json as part of this task (git dependency, ADR-2) so the build resolves the real import — there is no registry wait to gate on.

Acceptance criteria

  • Toggle ON + sso_id present ⇒ Session constructed exactly once with currentUser = 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 calls session.destroy() (not .off() alone) — resets the SDK singleton and tears down the iframe + window listener (R11, AGENTS.md leak rule).
  • mekari-account-web-sdk added to package.json as a git dependency; dynamic import resolves at build.

Test strategy

Jest unit test on the mixin in isolation. Mock mekari-account-web-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({ currentUser: '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 for isolation even though there's no registry blocker; @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: centralized_session org-payload flag — OQ-2 (pending; toggle inert until shipped, but does not block dev/test behind a mocked flag)]

Task 2: [FE] Wire mixin into boot + handle logged_out (Chunk 3 · story "Handle logged_out")

When SSO reports the user is logged out — including an SSO account switch, which the SDK cannot distinguish from a plain logout and never exposes the incoming user's ssoId for (R5) — Hub signs the user out automatically. logged_in is captured by the same dispatcher but takes no action here; it is wired to company sync in Task 6.

Status: ✅ Actionable — the sign-out path maps onto a primitive that already exists and is verified (EventBus.$emit('user-sign-out')doSignOut()).

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 the single-arg session.on('event', handleSessionEvent) dispatcher with a logged_out branch: emit EventBus.$emit('user-sign-out') (the relay SwitchAccount.vue:299 already turns into doSignOut()). The dispatcher also has a logged_in case that is intentionally a no-op placeholder until Task 6 wires company sync.

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.jsImplement handleSessionEvent(data): switch (data.status) { case 'logged_out': EventBus.$emit('user-sign-out'); break; case 'logged_in': /* no-op here — see Task 6 */ break; }. Import EventBus from @/plugins/event-bus.
extendassets/mixins/session/__test__/centralizedSession.spec.jsTest: { status: 'logged_out' }EventBus.$emit called once with 'user-sign-out'; { status: 'logged_in' } ⇒ no EventBus.$emit call (no-op).

All paths repo-verified. No utils/general.js changes and no msli read/write anywhere — the SDK owns msli internally and Hub must not touch it (R7).

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.
  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 no side effect. Run npm run test -- --testPathPattern="centralizedSession" → red.
  3. Wire state — In the mixin import { EventBus } from @/plugins/event-bus.
  4. Implement behavior — Fill handleSessionEvent's logged_out and logged_in branches (the latter a documented no-op); register the mixin in InitComponent.vue and invoke initSession() after $auth.loggedIn.
  5. Go greennpm run test -- --testPathPattern="centralizedSession" until green.
  6. Quality gatenpm run lint && npm run build.

Acceptance criteria

  • logged_out event ⇒ EventBus.$emit('user-sign-out') invoked exactly once.
  • logged_in event ⇒ handled by the dispatcher with no side effect (no localStorage/msli access — R7).
  • 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. Key assertion: expect(EventBus.$emit).toHaveBeenCalledWith('user-sign-out') for logged_out, and expect(EventBus.$emit).not.toHaveBeenCalled() for logged_in.

Effort estimate

DisciplineDays
Frontend1
Backend
QA0.5
Total1.5

Assumptions: reuses the verified user-sign-out EventBus relay and doSignOut() (no new sign-out code). Effort reduced from the original 2-day estimate: the msli-write helper work (writeMsli/readMsli/removeMsli in utils/general.js) is removed entirely — the SDK owns msli internally and a consumer write would collide with its own fallback (R7).

Run to verify

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

Depends on

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

Task 4: [FE] Handle server_down — fail-open, no action (Chunk 5 · story "Handle server_down")

When the Session Manager is unreachable, Hub does not sign the user out, change company, or take any other visible action. The SDK already exhausts its own internal msli-based grace period (localStorage key "msli", 2h expiry, owned and read/written by the SDK itself) before ever emitting server_down, and PRD constraint 6.10 requires fail-open behaviour on this path. This replaces the original design's consumer-side msli heuristic, which would have required Hub to read/write an SDK-internal key — a correctness risk, not a refinement (R7).

Status: ✅ Actionable.

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

What to build

A server_down branch in handleSessionEvent (ADR-6) that takes no action — no EventBus.$emit, no localStorage access, no token-validity check. Optional observability hook is wired in Task 5.

Implementation Plan

ActionFileWhat changes
extendassets/mixins/session/centralizedSession.jscase 'server_down': explicit no-op (fail-open, ADR-6), with a code comment referencing R7/ADR-6 so it isn't "helpfully" reintroduced later.
extendassets/mixins/session/__test__/centralizedSession.spec.jsTest: server_down event ⇒ EventBus.$emit (and any sign-out path) is not invoked.

All paths repo-verified. No utils/general.js helper (isMsliFresh or similar) is built — there is no consumer-side msli/token-validity logic to implement (R7).

Implementation steps

  1. Explore — Re-read ADR-6 (hub-fe.md §2.3) for the fail-open rationale and the state machine (§2.7) self-loop on server_down.
  2. Write failing tests (red) — Extend the spec with { status: 'server_down' } ⇒ assert EventBus.$emit was not called. Run npm run test -- --testPathPattern="centralizedSession" → red.
  3. Implement behavior — Add the server_down case as an explicit, commented no-op.
  4. Go greennpm run test -- --testPathPattern="centralizedSession" until green.
  5. Quality gatenpm run lint && npm run build.

Acceptance criteria

  • server_down ⇒ no EventBus.$emit('user-sign-out'), no company-state change, no localStorage access.
  • No consumer-side msli or token-validity logic exists anywhere in the mixin.

Test strategy

Jest. Dispatch { status: 'server_down' } into handleSessionEvent; assert no side-effecting spy (EventBus.$emit) was called.

Effort estimate

DisciplineDays
Frontend0.5
Backend
QA0.5
Total1

Assumptions: fail-open is a pure no-op branch — no new persistence, no predicate to name (unlike the original design's msli/token-validity check). Effort reduced from the original 2-day estimate accordingly.

Run to verify

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

Depends on

  • [Task 1] (mixin scaffold)

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.<status> per handled SDK event, and log forced sign-outs (logged_out) to Mixpanel for funnel analysis (§3.3). There is no switch_user status to track separately (R5).

Implementation Plan

ActionFileWhat changes
extendassets/mixins/session/centralizedSession.jsIn handleSessionEvent, after each branch, emit datadogRum.addAction('centralized_session.' + data.status, {...}); for the logged_out (sign-out) path 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.<status>; logged_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.
  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.<status> (verifiable via mocked tracker).
  • Forced sign-outs (logged_out) 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 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. This is a Hub BE/SSO dependency, decoupled from the SDK (R10): the SDK exposes no company data and does not "drive" this sync beyond firing the logged_in event Hub already listens for.

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. Also gated by OQ-2 (centralized_session org flag) before the path executes. 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).

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. Because an SSO account switch now surfaces as logged_out → full sign-out → normal re-login (R5), the subsequent logged_in event naturally re-triggers this same sync — there is no separate "post-switch" case to implement.

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, 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 (replacing the Task 2 no-op placeholder).
  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 an SSO account switch (which now surfaces as logged_out → full sign-out → new login), the next logged_in naturally re-syncs the company — no special-case handling needed (R5).
  • (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
Backend0
QA0.5
Total1.5

Assumptions: BE = 0 here to avoid a double-count — Hub (Chat v1) and Hub Chat v2 are two frontends of the same hub-core/hub-service backend, so the current-company BE endpoint is built once and its 2.0 BE is carried in hub-chat-v2-fe.task-breakdown.md Task 7. Hub's FE still consumes it (FE 1.0 retained). The company-sync spec itself is still blocked on the real contract (OQ-1); 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 placeholder 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; the SDK dependency (mekari-account-web-sdk) is a straightforward git install (ADR-2, OQ-3 resolved), so nothing blocks starting this task immediately. Tasks 2, 4, 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).
  • Task 2 → Task 4 are ordered by primitive reuse: logged_out (Task 2) establishes the sign-out relay and the dispatcher's switch skeleton; server_down (Task 4) is an independent no-op branch added to the same dispatcher.
  • 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 four are executable today behind the (currently OFF) toggle, exactly as the RFC's §5 "Known limitation" states.
  • Task 3 (switch_user re-auth) has been removed entirely — the real SDK (mekari-account-web-sdk v0.3.0) cannot emit a switch_user status. An SSO account switch surfaces as logged_out, which Task 2 already handles via the existing sign-out flow; the subsequent re-login naturally triggers a fresh logged_in event, which Task 6 (once unblocked) re-syncs company for — no dedicated re-auth/toast code is needed (R5). The numbering below intentionally keeps the gap at 3 rather than renumbering, to preserve traceability to the RFC's chunk numbers.
  • Push externally on two fronts in parallel with Task 1: Hub BE to build the current_company proxy (OQ-1) and add the centralized_session org flag (OQ-2). These are now the only remaining blockers — the previous SDK-side blockers (OQ-3 package publish, OQ-4 event contract) are resolved.

Skipped stories

(Full-scope mode: every 🚫 Blocked task listed with its unblocking condition.)

Story / TaskReason / unblock condition
Task 3 — switch_user re-auth (removed entirely)Not a story to unblock — it never existed. The real SDK (mekari-account-web-sdk v0.3.0) has no switch_user status; an account switch surfaces as logged_out and is handled by Task 2. Removed per R5 (latest review reconciliation); ~2 FE + 0.5 QA days dropped from the original estimate.
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. Decoupled from the SDK (R10) — the SDK exposes no company data; this is purely a Hub BE/SSO dependency.
(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-account-web-sdk package, Session Manager (Golang), dedicated Redis, SSO Kong sm.mekari.com/*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.