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/sdkpackage, and the Kongaccount.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 asdeferred).
Metadata
| Field | Value | Notes |
|---|---|---|
| Status | RFC | IDEA | RFC | ABANDON | AGREED |
| Type / Sub-type | frontend / new-feature | Launchpad SPA integration |
| Title | Centralized Web Session — Launchpad SDK Integration | |
| Owner | Account & Launchpad (AL) | Pilot product squad |
| Authors | Syafrizal Muhammad | |
| Reviewers | AL squad reviewer; SSO/Session Manager BE reviewer | [REQUIRED] names |
| Approver(s) | AL tech lead; infosec approver | infosec mandatory (SDK/iframe/CSP) |
| Submitted | 2026-06-27 | |
| Last updated | 2026-06-27 | |
| Target release | [REQUIRED] | |
| Related Documents | Centralized Web Session PRD | Confluence PT space |
| Discussion | [REQUIRED] |
Sections at a Glance
| § | Section | Frontend hints |
|---|---|---|
| 1 | Overview | Problem, success criteria, PRD-to-behavior derivation, decisions index, per-story change map |
| 2 | Technical Design | Repo Reading Guide, topology, SDK contract, event routing, state surface, sequence diagrams, state machine, branch catalog |
| 3 | High-Availability & Security | CSP/frame-ancestors, referrer/origin, token handling, OWASP |
| 4 | Backwards Compatibility & Rollout | Feature flag, execution plan, verification & rollback recipe |
| 5 | Concerns / Open Questions | Severity-tagged blockers |
| 6 | Comment logs | Review history |
| 7 | Ready for agent execution | Gate 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)
| # | Criterion | FE-observable assertion |
|---|---|---|
| SC1 | SDK reports session status across products | Launchpad reacts to all 4 SDK events (test-asserted in event-handler spec) |
| SC2 | Session expires after 2h idle | On logged_out event Launchpad runs sign-out flow (no manual reload needed) |
| SC3 | Activity refreshes session | session.refresh() called on user activity, throttled (interval TBD — OQ-6) |
| SC4 | 1 account → multiple sessions | No 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/sdkpackage, 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 section | Covered in | Status |
|---|---|---|
| 1. Overview (issues) | §1.1 | covered |
| Success Criteria | §1.2 | covered |
| Out of Scope | §1.3 | covered |
| Dependencies (SDK, Session Manager, Redis) | §2.2 topology, §5 OQ-1/OQ-2 | covered (as external deps) |
| 2. Technical Design — Current/Proposal | §2.4, §2.5 | covered |
| How to use the SDK | §2.4 | covered |
Local Storage (msli) | §2.6 | covered |
Cookies (_mekari_account) | §2.6 | covered |
| SDK contract (events) | §2.4 | covered |
| FE Product Integration — SDK Flow | §2.7 seq | covered |
| FE — Web Session Flow | §2.7 | n/a — Launchpad is OAuth2 (see ADR-3) |
| FE — OAuth2 Authorization Code Flow | §2.7 seq | covered (Launchpad's model) |
| User Logout From Product | §2.7, §2.9 | covered |
| User Switch Account | §2.7 switch_user seq | covered |
| Database Model (no change) | §2.8 | covered |
| 3. HA & Security | §3 | covered (FE-side: CSP, referrer) |
| 4. Rollout Plan | §4 | covered (step 4 only; 5 deferred) |
| 5. Open Questions | §5 | covered |
| FE Implementation Scope | §4.C execution plan | covered |
UI / Consumer Surface Coverage
| Surface | Trigger | Backing read | Coverage |
|---|---|---|---|
| Every authenticated Launchpad page | SDK loaded app-wide via plugin | SDK iframe → Session Manager | §2.7 SDK flow |
| "User has changed" notification | switch_user event | toast.notify (@mekari/pixel3) | §2.7, reuses authStore toast pattern |
| Session-expired toast + redirect | logged_out / server_down | existing authenticated.global.ts:97-110 pattern | §2.9 |
Role Coverage
| Role | Behavior delta | Coverage |
|---|---|---|
| All authenticated Launchpad roles | Identical 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)
| # | Decision | Chosen | ADR |
|---|---|---|---|
| 1 | Feature-flag mechanism | New centralized_session key in configs/*.json → runtimeConfig.public | ADR-1 |
| 2 | Where SDK loads | New Nuxt plugin ~/plugins/centralized-session.ts, registered after auth.ts | ADR-2 |
| 3 | Which PRD flow applies | OAuth2 authorization-code flow (Launchpad confirmed response_type=code) | ADR-3 |
| 4 | current_user source | launchpad.sso_id cookie / authProfile.sso_id | ADR-4 |
| 5 | current-company sync | Reuse /users/me (authStore.fetchAuthLaunchpad) unless OQ-4 forces new endpoint | ADR-5 |
| 6 | Event→action routing | Central composable useCentralizedSession, reuses authStore.resetAuth + existing logout redirect | ADR-6 |
1.6 Detail 1.C — Per-Story Change Map
| Story | Layer scope | Changes | Acceptance criteria | RFC anchors | Notes |
|---|---|---|---|---|---|
| Detect SSO logout on Launchpad | FE-only | useCentralizedSession composable; plugin load | logged_out event → sign-out redirect; spec passes | §2.4, §4.C ch.3 | |
| React to SSO account switch | FE-only | switch_user handler → destroy session + re-auth + "user changed" toast | spec asserts redirect to /auth?... and toast | §2.7, §4.C ch.4 | |
| Refresh session on activity | FE-only | throttled session.refresh() on activity | refresh called ≤ once / throttle window | §2.4, §4.C ch.5 | throttle TBD OQ-6 |
server_down fallback | FE-only | msli localStorage read + fallback decision | msli < 2h + valid token → logged_in; else sign-out | §2.6, §4.C ch.5 | |
| Feature gate | Config | centralized_session flag in configs/*.json | flag off → zero behavior change (spec) | §4.A, §4.C ch.1 | |
| Multiple sessions / account | n/a — BE-owned | none | n/a | — | SC4 |
| CRM/Hub/HubChat integration | Cross-squad | per-repo SDK wiring | n/a | — | deferred — 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:Line | What to learn |
|---|---|---|
| 1 | app/common/composables/useClient.ts:9-15,98-143 | HTTP wrapper + 401 singleton refresh; do not duplicate refresh logic |
| 2 | app/common/composables/useAuthCookies.ts:5-30 | Token cookie keys (global_sso_token, global_sso_refresh_token) + launchpad.sso_id (line 30) |
| 3 | app/common/store/authStore.ts:30,135-149 | useAuthStore, setProfileAuth sets LAUNCHPAD_SSO_ID, resetAuth clears state |
| 4 | app/middleware/authenticated.global.ts:43-120 | Existing refresh + session-expired toast + logout redirect to CHATPANEL_URL/logout |
| 5 | app/features/sso-callback/composable/ssoCallback.ts:3-15 | OAuth2 code-flow redirect URL shape (response_type=code&scope=sso:profile) |
| 6 | app/plugins/auth.ts + nuxt.config.ts:89-95 | Plugin registration + load order (auth runs last) |
| 7 | app/plugins/pixel.ts, app/plugins/mixpanel.ts | Pattern for initializing a 3rd-party SDK in a Nuxt plugin |
| 8 | nuxt.config.ts:131-149 | runtimeConfig.public + env spread (line 148) — where flags surface |
| 9 | configs/development.json (+ local/production) | Env config files; add centralized_session here |
| 10 | app/common/composables/useErrorHandler.spec.ts, useAuthCookies.spec.ts | Vitest + vi.mock pattern for composable specs |
Patterns to Follow
| Concern | Reference file | Pattern |
|---|---|---|
| State management | app/common/store/authStore.ts:30 | Pinia defineStore setup-style, ref() state, returned actions |
| 3rd-party SDK init | app/plugins/pixel.ts:1-10, app/plugins/mixpanel.ts | defineNuxtPlugin, init inside plugin, register in nuxt.config.ts:89-95 |
| HTTP / refresh | app/common/composables/useClient.ts:98-112 | singleton refreshPromise to dedupe refresh |
| Error normalization | app/common/composables/useErrorHandler.ts | route API errors through handleError |
| User-facing notice | app/middleware/authenticated.global.ts:97-101 | toast.notify from @mekari/pixel3 |
| Logout redirect | app/middleware/authenticated.global.ts:109,116-119 | window.location.replace(CHATPANEL_URL/logout) / navigateTo(... {external:true}) |
| Cookie access | app/common/composables/useAuthCookies.ts:9-30 | useCookie with prod COOKIE_DOMAIN else localhost |
| Test | app/common/composables/useErrorHandler.spec.ts | Vitest, vi.mock, co-located .spec.ts |
No in-repo pattern exists for iframe injection /
postMessagelistening — the@mekari/sdkencapsulates the iframe, so Launchpad code never injects one directly. Launchpad only consumes SDK events. (No fabricated pattern.)
Reading Order for the Agent
app/common/composables/useAuthCookies.tsapp/common/store/authStore.tsapp/common/composables/useClient.tsapp/middleware/authenticated.global.tsapp/features/sso-callback/composable/ssoCallback.tsapp/plugins/auth.tsthenapp/plugins/pixel.tsnuxt.config.ts(plugins + runtimeConfig)configs/development.jsonapp/common/composables/useErrorHandler.spec.ts
Source Verification
| Claim | Evidence |
|---|---|
| HTTP client + 401 refresh singleton | useClient.ts:43 $fetch, :51 if (errorStatus === 401), :98 performTokenRefresh, :100 if (refreshPromise) |
| Token cookie keys | useAuthCookies.ts:5 "global_sso_token", :6 "global_sso_refresh_token", :30 useCookie("launchpad.sso_id") |
| Auth store + sso_id write | authStore.ts:30 defineStore("auth-profile"...), :139 LAUNCHPAD_SSO_ID.value = payload?.sso_id ?? "", :142 resetAuth |
| Launchpad is OAuth2 code flow | ssoCallback.ts:6 ...response_type=code&scope=sso:profile&redirect_uri=... ; mirrored in authenticated.global.ts:80 |
| Plugin registration/order | nuxt.config.ts:89-95 plugins array, auth.ts last |
| 3rd-party SDK init pattern | plugins/pixel.ts:1-10 defineNuxtPlugin, nuxtApp.vueApp.use(PixelPlugin, ...) |
| Flags surface via env spread | nuxt.config.ts:148 ...CONFIGENVIRONMENT.env into runtimeConfig.public |
| Config env files exist | configs/development.json, local.json, production.json (listed) |
| Logout redirect target | authenticated.global.ts:109 window.location.replace(\${config.public.CHATPANEL_URL}/logout`)` |
| Test command + framework | package.json:16 "test": "vitest --dom --pool=forks", :19 vue-tsc --noEmit, specs in app/common/composables/*.spec.ts |
@mekari/sdk NOT present | package.json:63-77 deps — no @mekari/sdk (→ OQ-1) |
| Session Manager / Redis NOT in FE repos | absent from all 4 repos (→ OQ-2) |
current_company not called in Launchpad | no match in repo; company from authStore.ts:88 data.value.data → authProfile.company_id (→ OQ-4) |
Cross-Service Responsibility Map
| Step | Action | Owner |
|---|---|---|
Publish @mekari/sdk + /sm/sdk.js | SDK package + CDN | SSO/Account (BE) |
Session Manager /sm/current + Redis + timeoutable | iframe page, session store | SSO/Account (BE) |
Kong account.mekari.com/sm/* routing + MAG | gateway | Platform/SSO |
CSP frame-ancestors whitelist update mechanism | config repo | SSO/Account (BE) + infosec |
| Consume SDK events, gate behind flag, wire logout/switch | Launchpad FE (this RFC) | AL squad |
| CRM / Hub / Hub Chat v2 wiring | per-repo | respective squads (deferred) |
Existing Contracts check (endpoints)
| Contract | Tag | Evidence / justification |
|---|---|---|
GET {apiBaseUrl}/users/me (profile + company) | reused | authStore.ts:81 — primary company source |
POST {apiBaseUrl}/seamless/refresh_sso_token | reused | useClient.ts:120, authenticated.global.ts:47 |
SSO /auth/?...response_type=code | reused | ssoCallback.ts:6 |
{SSO}/sign_out?client_id=... / CHATPANEL_URL/logout | reused | existing logout redirect |
account.mekari.com/sm/current (iframe, via SDK) | new-with-justification | provided by SDK iframe; Launchpad never calls directly — only consumes events (BE dep, OQ-2) |
SSO /v1.1/users/me/current_company | new-with-justification | PRD-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
| Service | Use cases (FE-relevant) | FE calls | 3rd-party |
|---|---|---|---|
| Launchpad SPA | render 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_id | n/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
| Event | Meaning | Launchpad action (OAuth2 flow, ADR-3/ADR-6) |
|---|---|---|
logged_in | session exists, same user_sso_id | continue; ensure company synced via /users/me (ADR-5) |
logged_out | no session (logout or 2h idle) | revoke flow → sign-out (reuse authenticated.global.ts:109 redirect) |
switch_user | session exists, different user_sso_id | authStore.resetAuth() + clear token cookies → SSO /auth?... re-auth → resync company → "user has changed" toast |
server_down | SDK exhausted backoff retries | msli fallback (§2.6); on fail → sign-out (PRD suggested) |
2.5 State Surface Contract
| State | Source | Surfaced to | Where |
|---|---|---|---|
current_user | launchpad.sso_id cookie / authProfile.sso_id | SDK constructor | useCentralizedSession |
| session status | SDK event data.status | route guard / handler | useCentralizedSession |
msli | localStorage timestamp | fallback decision | useCentralizedSession |
| company context | /users/me → authProfile.company_id | UI | authStore (reused) |
2.6 Local Storage & Cookies
| Key | Type | Owner | Notes |
|---|---|---|---|
msli (Mekari Session Logged In) | localStorage timestamp | Launchpad (set by SDK/handler) | fallback when Session Manager down; valid if now−msli < 2h |
_mekari_account | cookie on account.mekari.com | SSO (Rails) | not readable by Launchpad JS (cross-domain); only the SDK iframe (same-origin to account.mekari.com) uses it |
global_sso_token / _refresh_token | cookie .qontak.* | Launchpad existing | useAuthCookies.ts:5-7 |
launchpad.sso_id | cookie | Launchpad existing | useAuthCookies.ts:30 — current_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
| Branch | Condition | Action | Owner |
|---|---|---|---|
| Flag off | centralized_session false | SDK not loaded; behavior unchanged | FE |
| Excluded pages | sso-callback, login, expired (authenticated.global.ts:11-20) | skip session check | FE |
server_down + healthy msli | now−msli < 2h & token valid | stay logged in (degraded) | FE |
server_down + stale msli | else | sign-out | FE |
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:
| Area | Control | OWASP |
|---|---|---|
| SDK origin | Page CSP restricts SDK script-src to account.mekari.com; iframe frame-ancestors whitelists launchpad.* (whitelist update = BE config repo, OQ-3) | A05 Misconfig |
| Token handling | Tokens stay in existing cookies; no new token storage; never log tokens (AGENTS.md) | A02 Crypto / A09 Logging |
postMessage | SDK validates event.origin === account.mekari.com; Launchpad trusts SDK events only (verify SDK enforces origin — OQ) | A08 Integrity |
| XSS via iframe content | iframe renders empty page w/ user_sso_id only; no untrusted HTML injected into Launchpad | A03 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)
| Step | Command | Source |
|---|---|---|
| Lint | pnpm run lint | package.json:14 |
| Type check | pnpm run type-check | package.json:19 |
| Unit tests | pnpm run test | package.json:16 |
| Build | pnpm run build | package.json:7 |
4.C Agent Execution Plan (ordered chunks)
Blocked prerequisite: chunks 2–5 import
@mekari/sdkand 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 === falseby 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; modifynuxt.config.ts:89-95(register afterauth.ts) - Commands:
pnpm run lint && pnpm run build - Acceptance: when flag on,
@mekari/sdkSessioninstantiated once withcurrent_userfromlaunchpad.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_user→resetAuth+/authredirect + 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, reuseauthStore.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 +
msliremoved before/authredirect.
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 → treatedlogged_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_downrate 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):
- Set
centralized_session: falseinconfigs/production.jsonand redeploy (SDK stops loading; app reverts to current behavior). - Confirm no
@mekari/sdknetwork request to/sm/sdk.jsin prod. - Confirm session-expired toast/redirect rate returns to baseline.
- 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)
| Stage | Audience | Go/No-go |
|---|---|---|
| 4 Taking Off | Launchpad pilot cohort (flag on) | SDK events healthy, server_down < threshold |
| 5 Next Chapter | CRM/Hub/HubChat (deferred) | per-repo RFC; add domain to CSP whitelist |
5. Concerns / Open Questions
- OQ-1 [critical]
@mekari/sdkis not inpackage.json(:63-77) and the URLaccount.mekari.com/sm/sdk.jsis 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 iframepostMessagecontract. Need: a staging/sm/currentendpoint 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 wiringserver_downtiming and CSPframe-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'scurrent_companyendpoint is required for OAuth2 flow. - OQ-5 [important] SDK
postMessageorigin enforcement — confirm the SDK validatesevent.originso 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_useron a stale token (switch_user pre-detection) —launchpad.sso_idmay hold the previous account's id; confirm SDK comparison still yieldsswitch_usercorrectly.
6. Comment logs
| Date | Author | Comment |
|---|---|---|
| 2026-06-27 | Syafrizal Muhammad | Initial 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.