Skip to main content

RFC: Centralized Web Session — Launchpad SDK Integration

Document Conventions (do not remove)

Follows the Qontak RFC Template governance (metadata table + sections 1–6 + Comment logs) and is agent-execution-ready (§1 Design/PRD derivation, §2 Repo Reading Guide, mermaid diagrams, §4 Execution Plan + Verification/Rollback). YAML frontmatter and the metadata table must agree. ISO-8601 dates.

Scope note. This is the frontend RFC for the Launchpad pilot only (Rollout step 4 "Taking Off"). The Session Manager service, its Redis, the @mekari/sdk package, and the Kong account.mekari.com/sm/* routing are upstream backend dependencies owned by SSO/Account — they are tracked here as external contracts, not built by this RFC. CRM / Hub / Hub Chat v2 integration is Rollout step 5 and is out of scope for execution (mapped in Detail 1.C as deferred).

Metadata

FieldValueNotes
StatusRFCIDEA | RFC | ABANDON | AGREED
Type / Sub-typefrontend / new-featureLaunchpad SPA integration
TitleCentralized Web Session — Launchpad SDK Integration
OwnerAccount & Launchpad (AL)Pilot product squad
AuthorsSyafrizal Muhammad
ReviewersAL squad reviewer; SSO/Session Manager BE reviewer[REQUIRED] names
Approver(s)AL tech lead; infosec approverinfosec mandatory (SDK/iframe/CSP)
Submitted2026-06-27
Last updated2026-06-27
Target release[REQUIRED]
Related DocumentsCentralized Web Session PRDConfluence PT space
Discussion[REQUIRED]

Sections at a Glance

§SectionFrontend hints
1OverviewProblem, success criteria, PRD-to-behavior derivation, decisions index, per-story change map
2Technical DesignRepo Reading Guide, topology, SDK contract, event routing, state surface, sequence diagrams, state machine, branch catalog
3High-Availability & SecurityCSP/frame-ancestors, referrer/origin, token handling, OWASP
4Backwards Compatibility & RolloutFeature flag, execution plan, verification & rollback recipe
5Concerns / Open QuestionsSeverity-tagged blockers
6Comment logsReview history
7Ready for agent executionGate marker

1. Overview

1.1 Problem

Mekari products keep independent web sessions and never observe SSO logout. A user who logs out on SSO (or switches account on SSO) stays "logged in" on Launchpad with stale company/account context. Concretely (PRD §1): after an SSO logout the user remains active on the product, and after an SSO account switch the product still shows the previous company. Launchpad today holds the SSO tokens in cookies (app/common/composables/useAuthCookies.ts:5-30) and never re-validates them against SSO between navigations — authenticated.global.ts only refreshes an expired access token, it never asks "is this SSO session still the one I think it is?".

This RFC integrates the new @mekari/sdk Session SDK into Launchpad so the SPA detects, on every page, whether the SSO session is logged_in, logged_out, switch_user, or server_down, and reacts consistently with Launchpad's existing OAuth2 authorization-code auth.

1.2 Success Criteria (from PRD)

#CriterionFE-observable assertion
SC1SDK reports session status across productsLaunchpad reacts to all 4 SDK events (test-asserted in event-handler spec)
SC2Session expires after 2h idleOn logged_out event Launchpad runs sign-out flow (no manual reload needed)
SC3Activity refreshes sessionsession.refresh() called on user activity, throttled (interval TBD — OQ-6)
SC41 account → multiple sessionsNo FE change required; Launchpad treats each browser independently (Detail 1.C n/a — BE-owned)

1.3 Out of Scope

  • Auto-revoking access/refresh tokens on idle logout (PRD out-of-scope).
  • Building the Session Manager service, its Redis, the @mekari/sdk package, or Kong /sm/* routing (backend; tracked as dependencies).
  • CRM / Hub / Hub Chat v2 wiring (Rollout step 5 — deferred; mapped in 1.C).
  • Multiple-sessions-per-account FE work (no FE change; BE/SSO concern).

1.4 Detail 1.A — PRD Section Coverage

PRD sectionCovered inStatus
1. Overview (issues)§1.1covered
Success Criteria§1.2covered
Out of Scope§1.3covered
Dependencies (SDK, Session Manager, Redis)§2.2 topology, §5 OQ-1/OQ-2covered (as external deps)
2. Technical Design — Current/Proposal§2.4, §2.5covered
How to use the SDK§2.4covered
Local Storage (msli)§2.6covered
Cookies (_mekari_account)§2.6covered
SDK contract (events)§2.4covered
FE Product Integration — SDK Flow§2.7 seqcovered
FE — Web Session Flow§2.7n/a — Launchpad is OAuth2 (see ADR-3)
FE — OAuth2 Authorization Code Flow§2.7 seqcovered (Launchpad's model)
User Logout From Product§2.7, §2.9covered
User Switch Account§2.7 switch_user seqcovered
Database Model (no change)§2.8covered
3. HA & Security§3covered (FE-side: CSP, referrer)
4. Rollout Plan§4covered (step 4 only; 5 deferred)
5. Open Questions§5covered
FE Implementation Scope§4.C execution plancovered

UI / Consumer Surface Coverage

SurfaceTriggerBacking readCoverage
Every authenticated Launchpad pageSDK loaded app-wide via pluginSDK iframe → Session Manager§2.7 SDK flow
"User has changed" notificationswitch_user eventtoast.notify (@mekari/pixel3)§2.7, reuses authStore toast pattern
Session-expired toast + redirectlogged_out / server_downexisting authenticated.global.ts:97-110 pattern§2.9

Role Coverage

RoleBehavior deltaCoverage
All authenticated Launchpad rolesIdentical session lifecycle; no role-specific branch§2 — n/a — session is role-agnostic

Launchpad permission roles (permission.global.ts) are orthogonal to session validity; this RFC adds no role × session matrix beyond the existing permission middleware.

1.5 Detail 1.B — Decisions Closed (index → §2 ADRs)

#DecisionChosenADR
1Feature-flag mechanismNew centralized_session key in configs/*.jsonruntimeConfig.publicADR-1
2Where SDK loadsNew Nuxt plugin ~/plugins/centralized-session.ts, registered after auth.tsADR-2
3Which PRD flow appliesOAuth2 authorization-code flow (Launchpad confirmed response_type=code)ADR-3
4current_user sourcelaunchpad.sso_id cookie / authProfile.sso_idADR-4
5current-company syncReuse /users/me (authStore.fetchAuthLaunchpad) unless OQ-4 forces new endpointADR-5
6Event→action routingCentral composable useCentralizedSession, reuses authStore.resetAuth + existing logout redirectADR-6

1.6 Detail 1.C — Per-Story Change Map

StoryLayer scopeChangesAcceptance criteriaRFC anchorsNotes
Detect SSO logout on LaunchpadFE-onlyuseCentralizedSession composable; plugin loadlogged_out event → sign-out redirect; spec passes§2.4, §4.C ch.3
React to SSO account switchFE-onlyswitch_user handler → destroy session + re-auth + "user changed" toastspec asserts redirect to /auth?... and toast§2.7, §4.C ch.4
Refresh session on activityFE-onlythrottled session.refresh() on activityrefresh called ≤ once / throttle window§2.4, §4.C ch.5throttle TBD OQ-6
server_down fallbackFE-onlymsli localStorage read + fallback decisionmsli < 2h + valid token → logged_in; else sign-out§2.6, §4.C ch.5
Feature gateConfigcentralized_session flag in configs/*.jsonflag off → zero behavior change (spec)§4.A, §4.C ch.1
Multiple sessions / accountn/a — BE-ownednonen/aSC4
CRM/Hub/HubChat integrationCross-squadper-repo SDK wiringn/adeferred — Rollout step 5

2. Technical Design

2.0 Repo Reading Guide (read before writing code)

Repo Map (slice this RFC touches)

flowchart LR
subgraph LP["FE: qontak-launchpad-fe (MAIN — write here)"]
cfg["configs/*.json (modified)"]
nuxt["nuxt.config.ts (modified)"]
plug["plugins/centralized-session.ts (new)"]
comp["composables/useCentralizedSession.ts (new)"]
cookies["composables/useAuthCookies.ts (modified: add _mekari read helpers)"]
client["composables/useClient.ts (read)"]
store["store/authStore.ts (modified: resetAuth reuse)"]
mw["middleware/authenticated.global.ts (read/modified)"]
sso["features/sso-callback/composable/ssoCallback.ts (read)"]
plug --> comp
comp --> store
comp --> cookies
comp --> mw
end
subgraph EXT["External (BE deps — READ-ONLY contracts)"]
sdk["@mekari/sdk Session (account.mekari.com/sm/sdk.js)"]
sm["Session Manager + iframe (account.mekari.com/sm/current)"]
end
plug --> sdk
sdk -.iframe+postMessage.-> sm

Existing Code Anchors

#File:LineWhat to learn
1app/common/composables/useClient.ts:9-15,98-143HTTP wrapper + 401 singleton refresh; do not duplicate refresh logic
2app/common/composables/useAuthCookies.ts:5-30Token cookie keys (global_sso_token, global_sso_refresh_token) + launchpad.sso_id (line 30)
3app/common/store/authStore.ts:30,135-149useAuthStore, setProfileAuth sets LAUNCHPAD_SSO_ID, resetAuth clears state
4app/middleware/authenticated.global.ts:43-120Existing refresh + session-expired toast + logout redirect to CHATPANEL_URL/logout
5app/features/sso-callback/composable/ssoCallback.ts:3-15OAuth2 code-flow redirect URL shape (response_type=code&scope=sso:profile)
6app/plugins/auth.ts + nuxt.config.ts:89-95Plugin registration + load order (auth runs last)
7app/plugins/pixel.ts, app/plugins/mixpanel.tsPattern for initializing a 3rd-party SDK in a Nuxt plugin
8nuxt.config.ts:131-149runtimeConfig.public + env spread (line 148) — where flags surface
9configs/development.json (+ local/production)Env config files; add centralized_session here
10app/common/composables/useErrorHandler.spec.ts, useAuthCookies.spec.tsVitest + vi.mock pattern for composable specs

Patterns to Follow

ConcernReference filePattern
State managementapp/common/store/authStore.ts:30Pinia defineStore setup-style, ref() state, returned actions
3rd-party SDK initapp/plugins/pixel.ts:1-10, app/plugins/mixpanel.tsdefineNuxtPlugin, init inside plugin, register in nuxt.config.ts:89-95
HTTP / refreshapp/common/composables/useClient.ts:98-112singleton refreshPromise to dedupe refresh
Error normalizationapp/common/composables/useErrorHandler.tsroute API errors through handleError
User-facing noticeapp/middleware/authenticated.global.ts:97-101toast.notify from @mekari/pixel3
Logout redirectapp/middleware/authenticated.global.ts:109,116-119window.location.replace(CHATPANEL_URL/logout) / navigateTo(... {external:true})
Cookie accessapp/common/composables/useAuthCookies.ts:9-30useCookie with prod COOKIE_DOMAIN else localhost
Testapp/common/composables/useErrorHandler.spec.tsVitest, vi.mock, co-located .spec.ts

No in-repo pattern exists for iframe injection / postMessage listening — the @mekari/sdk encapsulates the iframe, so Launchpad code never injects one directly. Launchpad only consumes SDK events. (No fabricated pattern.)

Reading Order for the Agent

  1. app/common/composables/useAuthCookies.ts
  2. app/common/store/authStore.ts
  3. app/common/composables/useClient.ts
  4. app/middleware/authenticated.global.ts
  5. app/features/sso-callback/composable/ssoCallback.ts
  6. app/plugins/auth.ts then app/plugins/pixel.ts
  7. nuxt.config.ts (plugins + runtimeConfig)
  8. configs/development.json
  9. app/common/composables/useErrorHandler.spec.ts

Source Verification

ClaimEvidence
HTTP client + 401 refresh singletonuseClient.ts:43 $fetch, :51 if (errorStatus === 401), :98 performTokenRefresh, :100 if (refreshPromise)
Token cookie keysuseAuthCookies.ts:5 "global_sso_token", :6 "global_sso_refresh_token", :30 useCookie("launchpad.sso_id")
Auth store + sso_id writeauthStore.ts:30 defineStore("auth-profile"...), :139 LAUNCHPAD_SSO_ID.value = payload?.sso_id ?? "", :142 resetAuth
Launchpad is OAuth2 code flowssoCallback.ts:6 ...response_type=code&scope=sso:profile&redirect_uri=... ; mirrored in authenticated.global.ts:80
Plugin registration/ordernuxt.config.ts:89-95 plugins array, auth.ts last
3rd-party SDK init patternplugins/pixel.ts:1-10 defineNuxtPlugin, nuxtApp.vueApp.use(PixelPlugin, ...)
Flags surface via env spreadnuxt.config.ts:148 ...CONFIGENVIRONMENT.env into runtimeConfig.public
Config env files existconfigs/development.json, local.json, production.json (listed)
Logout redirect targetauthenticated.global.ts:109 window.location.replace(\${config.public.CHATPANEL_URL}/logout`)`
Test command + frameworkpackage.json:16 "test": "vitest --dom --pool=forks", :19 vue-tsc --noEmit, specs in app/common/composables/*.spec.ts
@mekari/sdk NOT presentpackage.json:63-77 deps — no @mekari/sdk (→ OQ-1)
Session Manager / Redis NOT in FE reposabsent from all 4 repos (→ OQ-2)
current_company not called in Launchpadno match in repo; company from authStore.ts:88 data.value.dataauthProfile.company_id (→ OQ-4)

Cross-Service Responsibility Map

StepActionOwner
Publish @mekari/sdk + /sm/sdk.jsSDK package + CDNSSO/Account (BE)
Session Manager /sm/current + Redis + timeoutableiframe page, session storeSSO/Account (BE)
Kong account.mekari.com/sm/* routing + MAGgatewayPlatform/SSO
CSP frame-ancestors whitelist update mechanismconfig repoSSO/Account (BE) + infosec
Consume SDK events, gate behind flag, wire logout/switchLaunchpad FE (this RFC)AL squad
CRM / Hub / Hub Chat v2 wiringper-reporespective squads (deferred)

Existing Contracts check (endpoints)

ContractTagEvidence / justification
GET {apiBaseUrl}/users/me (profile + company)reusedauthStore.ts:81 — primary company source
POST {apiBaseUrl}/seamless/refresh_sso_tokenreuseduseClient.ts:120, authenticated.global.ts:47
SSO /auth/?...response_type=codereusedssoCallback.ts:6
{SSO}/sign_out?client_id=... / CHATPANEL_URL/logoutreusedexisting logout redirect
account.mekari.com/sm/current (iframe, via SDK)new-with-justificationprovided by SDK iframe; Launchpad never calls directly — only consumes events (BE dep, OQ-2)
SSO /v1.1/users/me/current_companynew-with-justificationPRD-specified for OAuth2 flow; reuse /users/me preferred unless OQ-4 requires it

2.1 Current

Launchpad (ssr: false SPA, nuxt.config.ts:77) holds SSO tokens in cookies and only re-validates by refreshing an expired access token in authenticated.global.ts. No mechanism observes SSO logout or account switch.

2.2 Infrastructure / Deployment Topology

flowchart TB
user["User Browser"]
subgraph LPnet["launchpad.qontak.com / launchpad.mekari.io (SPA, static)"]
spa["Launchpad SPA + @mekari/sdk Session"]
end
subgraph acct["account.mekari.com (via Kong/MAG)"]
kong["Kong Gateway /sm/*"]
smjs["/sm/sdk.js (CDN)"]
smcur["Session Manager /sm/current (iframe page, Golang)"]
redis[("Dedicated Session Redis<br/>cache.t3.small, RDB")]
end
api["api.mekari.io Kong → Launchpad BE /users/me, /seamless/refresh_sso_token"]
user --> spa
spa -->|load SDK| smjs
spa -.iframe.-> kong --> smcur --> redis
smcur -.postMessage.-> spa
spa -->|profile/company, token refresh| api

Service use cases & third-party connections

ServiceUse cases (FE-relevant)FE calls3rd-party
Launchpad SPArender app; consume SDK events; gate by flag/users/me, /seamless/refresh_sso_token, SSO /auth, /sign_out@mekari/sdk (loads iframe)
Session Manager (BE dep)validate session in Redis, update last_request_at, render user_sso_idn/a (iframe, SDK-mediated)Redis

2.3 Database Model

No database changes (PRD §2 "Database Model: No database changes"). FE adds one localStorage key (msli) — see §2.6. n/a — no schema.

2.4 SDK Contract (consumed by Launchpad)

Input to SDK constructor (PRD):

import { Session } from "@mekari/sdk"
const session = new Session({ current_user: "<user sso ID>" }) // ADR-4: launchpad.sso_id
session.on("event", (data, error) => { /* route by data.status */ })
session.refresh() // throttled — interval OQ-6
EventMeaningLaunchpad action (OAuth2 flow, ADR-3/ADR-6)
logged_insession exists, same user_sso_idcontinue; ensure company synced via /users/me (ADR-5)
logged_outno session (logout or 2h idle)revoke flow → sign-out (reuse authenticated.global.ts:109 redirect)
switch_usersession exists, different user_sso_idauthStore.resetAuth() + clear token cookies → SSO /auth?... re-auth → resync company → "user has changed" toast
server_downSDK exhausted backoff retriesmsli fallback (§2.6); on fail → sign-out (PRD suggested)

2.5 State Surface Contract

StateSourceSurfaced toWhere
current_userlaunchpad.sso_id cookie / authProfile.sso_idSDK constructoruseCentralizedSession
session statusSDK event data.statusroute guard / handleruseCentralizedSession
mslilocalStorage timestampfallback decisionuseCentralizedSession
company context/users/meauthProfile.company_idUIauthStore (reused)

2.6 Local Storage & Cookies

KeyTypeOwnerNotes
msli (Mekari Session Logged In)localStorage timestampLaunchpad (set by SDK/handler)fallback when Session Manager down; valid if now−msli < 2h
_mekari_accountcookie on account.mekari.comSSO (Rails)not readable by Launchpad JS (cross-domain); only the SDK iframe (same-origin to account.mekari.com) uses it
global_sso_token / _refresh_tokencookie .qontak.*Launchpad existinguseAuthCookies.ts:5-7
launchpad.sso_idcookieLaunchpad existinguseAuthCookies.ts:30current_user source

2.7 Sequence Diagrams

Happy path — logged_in (OAuth2 flow)

sequenceDiagram
participant B as Browser (Launchpad SPA)
participant SDK as @mekari/sdk
participant IF as SM iframe (account.mekari.com)
participant SM as Session Manager
participant R as Session Redis
participant API as Launchpad BE (/users/me)
B->>SDK: new Session({current_user: launchpad.sso_id})
SDK->>IF: open iframe /sm/current (sends _mekari_account cookie)
IF->>SM: GET /sm/current
SM->>R: validate + update last_request_at (<2h)
R-->>SM: ok
SM-->>IF: render page w/ user_sso_id (cache max 5s)
IF-->>SDK: postMessage(user_sso_id)
SDK->>SDK: match current_user → set msli=now
SDK-->>B: event {status:'logged_in'}
B->>API: GET /users/me (sync company) [reused]
API-->>B: profile {company_id}
Note over B: continue, no redirect

Failure path — server_down (backoff exhausted)

sequenceDiagram
participant B as Browser (Launchpad SPA)
participant SDK as @mekari/sdk
participant IF as SM iframe
B->>SDK: new Session({current_user})
SDK->>IF: open iframe /sm/current
IF--xSDK: no response (retry w/ backoff, exhausted)
SDK-->>B: event {status:'server_down', error}
alt msli exists AND now-msli < 2h AND token valid
B->>B: treat as logged_in (degraded)
else
B->>B: sign-out flow (window.location.replace CHATPANEL_URL/logout)
end

switch_user (OAuth2 re-auth)

sequenceDiagram
participant B as Browser
participant SDK as @mekari/sdk
participant ST as authStore
participant SSO as account.mekari.com
SDK-->>B: event {status:'switch_user'}
B->>ST: resetAuth() + clear token cookies + remove msli
B->>SSO: redirect /auth?client_id&response_type=code&scope=sso:profile&redirect_uri=.../sso-callback
SSO-->>B: code (autologin via valid _mekari_account)
B->>B: sso-callback → exchange → new tokens → /users/me
B->>B: toast.notify("user has changed") + go home

2.8 Session State Machine

stateDiagram-v2
[*] --> Initializing: page load, flag on
Initializing --> LoggedIn: event logged_in
Initializing --> LoggedOut: event logged_out
Initializing --> SwitchUser: event switch_user
Initializing --> ServerDown: event server_down
LoggedIn --> SwitchUser: event switch_user
LoggedIn --> LoggedOut: event logged_out (2h idle / SSO logout)
ServerDown --> LoggedIn: msli<2h & token valid
ServerDown --> LoggedOut: msli stale / token invalid
SwitchUser --> Initializing: re-auth complete
LoggedOut --> [*]: redirect to sign-out

2.9 Branch & Skip Catalog

BranchConditionActionOwner
Flag offcentralized_session falseSDK not loaded; behavior unchangedFE
Excluded pagessso-callback, login, expired (authenticated.global.ts:11-20)skip session checkFE
server_down + healthy mslinow−msli < 2h & token validstay logged in (degraded)FE
server_down + stale mslielsesign-outFE

3. High-Availability & Security

The HA/perf targets for /sm/current (6k RPS, p95 < 100ms, Redis < 2ms) are backend-owned (PRD §3) — Launchpad does not host that endpoint. FE security:

AreaControlOWASP
SDK originPage CSP restricts SDK script-src to account.mekari.com; iframe frame-ancestors whitelists launchpad.* (whitelist update = BE config repo, OQ-3)A05 Misconfig
Token handlingTokens stay in existing cookies; no new token storage; never log tokens (AGENTS.md)A02 Crypto / A09 Logging
postMessageSDK validates event.origin === account.mekari.com; Launchpad trusts SDK events only (verify SDK enforces origin — OQ)A08 Integrity
XSS via iframe contentiframe renders empty page w/ user_sso_id only; no untrusted HTML injected into LaunchpadA03 Injection
Referrer/origin (alt)optional referrer/origin validation at SM (BE)A05

Monitoring (FE-side): count SDK event types + server_down rate via existing analytics plugin (plugins/mixpanel.ts); alert on server_down spike. Use console.error/console.warn for unexpected SDK states per AGENTS.md (no console.log in committed code).


4. Backwards Compatibility & Rollout Plan

4.A Feature flag contract (ADR-1)

Add "centralized_session": false to configs/{local,development,production}.json env block; it surfaces at runtimeConfig.public.centralized_session via the existing spread (nuxt.config.ts:148). Default off → zero behavior change. Flip per-env to pilot. (No env-var-only or remote-config system exists — ADR-1.)

4.B Pre-merge verification (from package.json)

StepCommandSource
Lintpnpm run lintpackage.json:14
Type checkpnpm run type-checkpackage.json:19
Unit testspnpm run testpackage.json:16
Buildpnpm run buildpackage.json:7

4.C Agent Execution Plan (ordered chunks)

Blocked prerequisite: chunks 2–5 import @mekari/sdk and require the live Session Manager. Until OQ-1/OQ-2 resolve, the agent can complete chunk 1 and stub the SDK behind an interface for chunks 3–5 specs, but cannot run an end-to-end integration test.

Chunk 1 — Feature flag (Config)

  • Files: configs/local.json, configs/development.json, configs/production.json, nuxt.config.ts (expose typed key if needed)
  • Commands: pnpm run type-check && pnpm run test
  • Acceptance: runtimeConfig.public.centralized_session === false by default; flag-off path renders app unchanged (spec).

Chunk 2 — SDK loader plugin (ADR-2) (blocked on OQ-1)

  • Files (new): app/plugins/centralized-session.ts; modify nuxt.config.ts:89-95 (register after auth.ts)
  • Commands: pnpm run lint && pnpm run build
  • Acceptance: when flag on, @mekari/sdk Session instantiated once with current_user from launchpad.sso_id; when off, not loaded (spec asserts no SDK import side-effect).

Chunk 3 — Event router composable (ADR-6)

  • Files (new): app/common/composables/useCentralizedSession.ts + .spec.ts
  • Commands: pnpm run test -- app/common/composables/useCentralizedSession.spec.ts
  • Acceptance (TDD, red first): spec mocks SDK and asserts — logged_out→sign-out redirect; switch_userresetAuth + /auth redirect + toast; logged_in→no redirect; server_down→fallback path. All 4 branches covered.

Chunk 4 — switch_user / logout wiring

  • Files: app/common/composables/useCentralizedSession.ts, reuse authStore.resetAuth (authStore.ts:142), reuse logout redirect (authenticated.global.ts:109)
  • Commands: pnpm run test && pnpm run type-check
  • Acceptance: spec asserts token cookies cleared + msli removed before /auth redirect.

Chunk 5 — refresh + msli fallback

  • Files: useCentralizedSession.ts
  • Commands: pnpm run test
  • Acceptance: session.refresh() invoked ≤ once per throttle window (fake timers); server_down + msli < 2h + valid token → treated logged_in; else sign-out. (Throttle window = OQ-6.)

Chunk 6 — pilot enable

  • Files: configs/production.json (flip flag for pilot cohort)
  • Commands: pnpm run build
  • Acceptance: post-deploy SDK event metrics emit; server_down rate within alert threshold.

4.D Verification & Rollback Recipe

Post-deploy signals: SDK event counts (mixpanel), server_down rate, no spike in session-expired toasts; manual: SSO logout in another tab → Launchpad tab redirects to sign-out within one navigation.

Rollback (ordered):

  1. Set centralized_session: false in configs/production.json and redeploy (SDK stops loading; app reverts to current behavior).
  2. Confirm no @mekari/sdk network request to /sm/sdk.js in prod.
  3. Confirm session-expired toast/redirect rate returns to baseline.
  4. Revert the chunk PRs if code-level rollback needed (FE-only; safe — no DB/back-compat coupling).

4.E Rollout stages (PRD §4, FE scope = step 4)

StageAudienceGo/No-go
4 Taking OffLaunchpad pilot cohort (flag on)SDK events healthy, server_down < threshold
5 Next ChapterCRM/Hub/HubChat (deferred)per-repo RFC; add domain to CSP whitelist

5. Concerns / Open Questions

  • OQ-1 [critical] @mekari/sdk is not in package.json (:63-77) and the URL account.mekari.com/sm/sdk.js is a backend deliverable. Blocks chunks 2–5 end-to-end. Need: published package name/version + CDN URL availability.
  • OQ-2 [critical] Session Manager service + Redis + Kong /sm/* not present in any FE repo; Launchpad cannot self-verify the iframe postMessage contract. Need: a staging /sm/current endpoint to integration-test against.
  • OQ-3 [critical] PRD names two iframe paths — /sm/current (§2) vs /sessionmanager/current (FE flow). Confirm the canonical path before wiring server_down timing and CSP frame-ancestors.
  • OQ-4 [important] Launchpad currently derives company from /users/me (authStore.ts:88), not /v1.1/users/me/current_company. Confirm whether reuse suffices (ADR-5) or the PRD's current_company endpoint is required for OAuth2 flow.
  • OQ-5 [important] SDK postMessage origin enforcement — confirm the SDK validates event.origin so Launchpad can trust events without its own listener.
  • OQ-6 [important] session.refresh() throttle interval is "TBD" in PRD — needed to make chunk 5 acceptance assertable.
  • OQ-7 [nice-to-have] current_user on a stale token (switch_user pre-detection) — launchpad.sso_id may hold the previous account's id; confirm SDK comparison still yields switch_user correctly.

6. Comment logs

DateAuthorComment
2026-06-27Syafrizal MuhammadInitial draft (FE Launchpad pilot scope).

7. Ready for agent execution

Ready for agent execution: no

Blocking gates:

  • G-D1/Dependency: OQ-1, OQ-2, OQ-3 are [critical] external/contract blockers — the SDK package, the live Session Manager, and the canonical iframe path must exist before chunks 2–6 can be executed and verified end-to-end.
  • Chunk 1 (feature flag) and chunk 3 specs (against a mocked SDK interface) are executable now; full integration is gated on the above.

Resolve OQ-1, OQ-2, OQ-3 → re-run §7. Optionally hand to rfc-reviewer for a second-pass score.