RFC: Centralized Web Session — Launchpad FE Integration
Document Conventions (do not remove)
Follows the Qontak RFC Template governance shell (metadata table + sections 1–6 + Comment logs) and is agent-execution-ready: §1 Design References, §2 Repo Reading Guide, mermaid diagrams, and §4 Agent Execution Plan + Verification & Rollback Recipe must be complete before §7 flips to yes. The YAML frontmatter is the machine-readable index; the metadata table below is the human record. Both must agree on shared fields.
Scope of THIS RFC: the Launchpad (this repo) FE integration of the centralized web-session system. The
mekari-sessionGolang service, the shared Redis, the SDK build, and the SSO Rails changes are owned by the Account & SSO squads and are out of scope here (referenced as upstream contracts only). Launchpad is the FIRST pilot per the PRD rollout (step 4).
Metadata
| Field | Value | Notes |
|---|---|---|
| Status | RFC | IDEA / RFC / ABANDON / AGREED |
| Type | frontend | Launchpad is a Nuxt 4 SPA (ssr: false) |
| Sub-type | new-feature | new SDK integration + session-event handling |
| Title | Centralized Web Session — Launchpad FE Integration | |
| Owner | Account & Launchpad | |
| Authors | Account & Launchpad team | |
| Reviewers | SSO/Session service reviewer; Launchpad BE reviewer | placeholders pending |
| Approvers | Launchpad tech lead; infosec | infosec approval required |
| Submitted | 2026-06-28 | ISO-8601 |
| Last updated | 2026-06-28 | matches latest edit |
| Target release | TBD | placeholder — see Open Questions |
| Related documents | Centralized Web Session PRD | authoritative driver |
| Discussion | TBD |
Sections at a Glance
| § | Section | FE hints |
|---|---|---|
| 1 | Overview | goals, scope, Design References, PRD coverage, Decisions index, Per-Story Change Map |
| 2 | Technical Design | Repo Reading Guide, SDK contract, event handling, current-company sync, diagrams |
| 3 | High-Availability & Security | SDK loading hardening, FE observability, perf expectations |
| 4 | Backwards Compatibility & Rollout | toggle rollout, Agent Execution Plan, Verification & Rollback Recipe |
| 5 | Concern / Questions / Known Limitations | Open Questions with severity |
| 6 | Comment logs | review trail |
| 7 | Ready for agent execution | the gate |
1. Overview
1.0 Problem
Launchpad maintains its own session (OAuth2 bearer tokens in cookies) that is
not synchronized with SSO. When a user logs out of SSO, switches accounts,
or is idle past the SSO timeout, Launchpad keeps showing the stale account and
company. The PRD asks every Mekari product FE to load a shared @mekari/sdk
Session SDK that reports authoritative session state (logged_in,
logged_out, switch_user, server_down) so the product can react. Launchpad
is the first pilot.
1.1 Goals (from PRD Success Criteria)
- Launchpad consults the centralized session via the SDK on page load.
- Launchpad reacts to SSO logout / idle-timeout (2h) by ending its own session.
- Launchpad detects account switches on SSO and re-syncs the current company.
- Behavior gated behind a
centralized_sessionfeature toggle.
1.2 Non-goals
- Building the
mekari-sessionGolang service, shared Redis, or SDK package (upstream squads). - Auto-revoking access/refresh tokens on inactivity timeout (PRD Out of Scope).
- Multiple-sessions-per-account FE UX (PRD rolls this in parallel, not part of the Launchpad pilot critical path).
1.3 Design References
Launchpad is a backend-of-FE integration, not a net-new visual surface. The
only user-visible UI is a "your account has changed" notification on
switch_user (PRD §"switch_user"). Launchpad already renders toasts via
@mekari/pixel3 toast.notify (see app/common/store/authStore.ts:117).
| Surface | Figma frame | Implementing file | Notes |
|---|---|---|---|
| "Account has changed" notification | n/a — reuse existing Pixel toast, no new Figma | app/common/store/authStore.ts:117 pattern | text-only toast on switch_user |
| Session-loading gate (blank/spinner while SDK resolves) | [REQUIRED: confirm with design whether a loading state is needed] | app/middleware/authenticated.global.ts | PRD: "Product FE waits for SDK response" when toggle on |
1.A PRD Coverage
PRD Section Coverage
| PRD section | Covered in | Status |
|---|---|---|
| 1. Overview / known issues | §1.0, §1.1 | covered |
| Success Criteria | §1.1, §2.x | covered (FE portion) |
| Out of Scope | §1.2 | covered |
| Dependencies (SDK, service, Redis) | §2.1 upstream contracts | covered (upstream, read-only) |
| 2. Technical Design — Proposal | §2.2–§2.6 | covered (FE portion) |
| SDK contract / events | §2.3 | covered |
| FE Product Integration Flows | §2.4–§2.6 | covered |
| Web Session Flow | §2.5 | n/a — Launchpad uses OAuth2 authz-code flow, not Rails web session |
| OAuth2 Authorization Code Flow | §2.5 | covered — Launchpad's actual auth model |
| User Logout From Product | §2.6 | covered |
| User Switch Account | §2.5 switch_user | covered |
| Database Model | — | n/a — no DB changes (FE) |
| 3. HA & Security | §3 | covered (FE-relevant subset) |
| 4. Rollout Plan | §4 | covered (Launchpad step 4) |
| 5. Open Questions | §5 | covered |
| FE Implementation Scope | §1.C, §4 | covered |
UI / Consumer Surface Coverage
| Surface | Read endpoint / source | Coverage |
|---|---|---|
| "Account changed" toast | SDK switch_user event | §2.4 |
| Session gate | SDK init resolution | §2.4 |
Role Coverage
| Role | Relevance | Coverage |
|---|---|---|
| Any authenticated Launchpad user | All session events apply uniformly | §2.4 |
| no per-role session behavior in PRD | — | n/a — session handling is role-agnostic |
1.B Decisions Closed (index → §2 ADRs)
| # | Decision | Chosen | ADR |
|---|---|---|---|
| D1 | Auth model variant for Launchpad | OAuth2 Authorization Code flow (not web-session) | ADR-1 |
| D2 | Where to load the SDK | *.client.ts Nuxt plugin | ADR-2 |
| D3 | Toggle mechanism | reuse useToggleQontakOne pattern, new centralized_session toggle | ADR-3 |
| D4 | Session event → app reaction wiring point | route middleware + auth store | ADR-4 |
| D5 | SDK distribution (npm vs CDN script) | unresolved — see Open Question Q1 | — |
1.C Per-Story Change Map
| Story (PRD) | Layer scope | Changes | Acceptance criteria | RFC anchors |
|---|---|---|---|---|
Load SDK with user_sso_id on every page | FE-only | app/plugins/mekariSession.client.ts (new), reads authProfile.sso_id | plugin instantiates Session({current_user}); unit test asserts SDK constructed with cookie launchpad.sso_id value | §2.2, §4.C chunk 2 |
Gate behind centralized_session | FE-only | useCentralizedSessionToggle.ts (new) | toggle off → no SDK side effects (test) | §2.3 ADR-3, §4.C chunk 1 |
Handle logged_in | FE + BE | sync current company after session confirmed | on logged_in, current company fetched & set; test stubs SDK event | §2.4, §2.5, §4.C chunk 4 |
Handle logged_out | FE-only | revoke tokens + product sign-out (OAuth2 variant) | event → resetAuth() + cookies cleared + redirect to SSO sign_out | §2.4, §4.C chunk 3 |
Handle switch_user | FE (+ BE for company) | re-auth via existing OAuth flow + "account changed" toast | event → tokens cleared → SSO_URL/auth?... re-auth → toast shown | §2.4, §2.5, §4.C chunk 5 |
Handle server_down | FE-only | msli fallback then product sign-out | fallback predicate evaluated; sign-out on exhaustion | §2.4, §4.C chunk 6 — blocked by Q2 |
Wire product logout → SSO sign_out | FE-only | already present | logout hits SSO_URL/sign_out?client_id= | §2.6, SwitchAccountContent.vue:117 (reuse) |
msli local-storage fallback | FE-only | new helper | msli written on logged_in, read on server_down | §2.4 — blocked by Q2 |
| Current-company sync | FE + BE | new composable + BE endpoint | company set post-session | §2.5 — blocked by Q3 |
2. Technical Design
2.0 Repo Reading Guide (read BEFORE writing)
Repo Map (slice this RFC touches):
flowchart LR
subgraph launchpad["FE: qontak-launchpad-fe (this repo)"]
plugin["plugins/mekariSession.client.ts (new)"]
toggle["composables/useCentralizedSessionToggle.ts (new)"]
sess["composables/useCentralizedSession.ts (new)"]
company["composables/useCurrentCompany.ts (new)"]
mw["middleware/authenticated.global.ts (modified)"]
authStore["store/authStore.ts (modified)"]
ssoStore["store/ssoCallbackStore.ts (read/reuse)"]
cookies["composables/useAuthCookies.ts (read/reuse)"]
client["composables/useClient.ts (read/reuse)"]
logout["SwitchAccountContent.vue / TheSwitchAccount.vue (reuse)"]
end
subgraph upstream["Upstream (READ-ONLY contracts)"]
sdk["@mekari/sdk Session SDK"]
sm["account.mekari.com/sm/current (Session Manager)"]
sso["SSO auth / sign_out / current_company"]
end
plugin --> sdk
plugin --> toggle
plugin --> sess
sdk -. postMessage events .-> sess
sess --> authStore
sess --> company
sess --> mw
mw --> ssoStore
authStore --> cookies
company --> client
client --> sso
sdk -. iframe .-> sm
logout --> sso
Existing Code Anchors
| File | What to learn |
|---|---|
app/middleware/authenticated.global.ts | the single auth gate; existing token-refresh + unified-logout flow; where to await SDK |
app/common/store/authStore.ts | fetchAuthLaunchpad, setProfileAuth (sets LAUNCHPAD_SSO_ID), resetAuth; authProfile.sso_id is the user SSO ID |
app/common/store/ssoCallbackStore.ts | setTokenLaunchpad/clearTokenLaunchpad, OAuth2 fetchOauthSSO (authorization_code) |
app/common/composables/useAuthCookies.ts | cookie names global_sso_token, global_sso_refresh_token, global_sso_valid_until, launchpad.sso_id; domain from config.public.COOKIE_DOMAIN |
app/common/composables/useClient.ts | central $fetch wrapper, 401 refresh singleton, handleAuthenticationFailure |
app/common/composables/useToggleQontakOne.ts | existing feature-toggle pattern to mirror for centralized_session |
app/features/sso-callback/composable/ssoCallback.ts | handleRedirection; SSO authz URL construction (SSO_URL/auth?client_id=...&response_type=code) |
app/layouts/components/SwitchAccountContent.vue | sign-out wiring → SSO_URL/sign_out?client_id= (line 117) — already matches PRD §2.6 |
app/app.vue / app/layouts/default.vue | mount points; $auth provided by plugins/auth.ts |
Patterns to Follow
| Concern | Reference file | Pattern |
|---|---|---|
| State management | app/common/store/authStore.ts | Pinia setup store, refs + actions |
| Feature toggle | app/common/composables/useToggleQontakOne.ts:11 | module-singleton TOGGLE_SOURCE switch |
| API call | app/common/composables/useClient.ts:43 | useClient(url, opts) → {data,error}; read body at data.value.data |
| Auth cookies | app/common/composables/useAuthCookies.ts:9 | useCookie with prod domain from config |
| Error normalize | app/common/composables/useErrorHandler.ts | route API errors through useErrorHandler (per AGENTS.md) |
| User notification | app/common/store/authStore.ts:117 | toast.notify({position,variant,title}) |
| SSO re-auth redirect | app/middleware/authenticated.global.ts:79 | SSO_URL/auth/?client_id=&response_type=code&scope=sso:profile&redirect_uri=BASE_URL/sso-callback |
| Logout redirect | app/layouts/components/SwitchAccountContent.vue:117 | clear cookies → SSO_URL/sign_out?client_id= |
| Composable test | app/common/composables/useAuthCookies.spec.ts | Vitest + happy-dom, mock #app |
Reading Order for the Agent
app/middleware/authenticated.global.tsapp/common/store/authStore.tsapp/common/store/ssoCallbackStore.tsapp/common/composables/useAuthCookies.tsapp/common/composables/useClient.tsapp/common/composables/useToggleQontakOne.tsapp/features/sso-callback/composable/ssoCallback.tsapp/layouts/components/SwitchAccountContent.vuenuxt.config.ts(runtimeConfig:SSO_URL,SSO_UNIFIED_CLIENT_ID,CHATPANEL_URL,BASE_URL,COOKIE_DOMAIN,apiBaseUrl)app/common/composables/useAuthCookies.spec.ts(test conventions)
2.0.1 Source Verification
| Item | Evidence |
|---|---|
| Auth gate is route middleware | app/middleware/authenticated.global.ts:5 defineNuxtRouteMiddleware; refresh at :44, unified logout at :90 |
| User SSO ID source | authStore.ts:139 LAUNCHPAD_SSO_ID.value = payload?.sso_id; cookie launchpad.sso_id at useAuthCookies.ts:30 |
| OAuth2 authz-code is Launchpad's model | ssoCallbackStore.ts:53 grantType: "authorization_code"; redirect builder authenticated.global.ts:80 |
| Token cookies | useAuthCookies.ts:5-7 global_sso_token/global_sso_refresh_token/global_sso_valid_until |
| Sign-out already hits SSO | SwitchAccountContent.vue:117 window.location.href = ${ssoBaseUrl}/sign_out?client_id=${ssoClientId} |
| Toggle pattern | useToggleQontakOne.ts:11 const TOGGLE_SOURCE = "always-on" |
| Central fetch wrapper | useClient.ts:9; 401 refresh :51; handleAuthenticationFailure :146 |
| Test stack | package.json: "test": "vitest --dom --pool=forks"; example spec useAuthCookies.spec.ts |
_mekari_account NOT a Launchpad cookie | grep of useAuthCookies.ts — only global_sso_* + launchpad.sso_id; no _mekari_account → conflict, see Q2 |
@mekari/sdk NOT installed | package.json dependencies — absent → see Q1 |
| current_company sync absent | grep current_company across **/*.{ts,vue} → no matches → new, see Q3 |
centralized_session toggle absent | grep centralized_session → no matches → new |
2.1 Upstream contracts (read-only, owned by SSO/Session squads)
| Contract | Shape | Owner |
|---|---|---|
SDK package @mekari/sdk | new Session({current_user}), .on("event", (data,error)=>{}), .refresh() | Account/SSO |
| SDK CDN | https://account.mekari.com/sm/sdk.js (CJS+ESM) | Account/SSO |
| Session Manager | iframe → account.mekari.com/sm/current (PRD also says /sessionmanager/current — Q5) | Account/SSO |
| Events | logged_in, logged_out, switch_user, server_down | Account/SSO |
| Current company | GET https://api.mekari.com/v1.1/users/me/current_company (OAuth2 me variant) | SSO |
| Shared cookie | _mekari_account (SSO-domain scoped) | SSO |
2.2 Proposal (Launchpad FE)
A *.client.ts Nuxt plugin loads the SDK, reads the current user_sso_id
(launchpad.sso_id cookie / authProfile.sso_id), instantiates
new Session({ current_user }), and forwards the four events into a
useCentralizedSession composable that maps each event to a Launchpad reaction
using existing store actions and redirect builders. All behavior is gated by
a new centralized_session toggle mirroring useToggleQontakOne.
2.3 SDK contract & event mapping (OAuth2 variant)
| Event | Meaning | Launchpad reaction (OAuth2 authz-code) | Reuses |
|---|---|---|---|
logged_in | session exists, same user_sso_id | write msli=now; sync current company; allow interaction | useClient, current-company composable |
logged_out | no session (logout or 2h idle) | revoke tokens → product sign-out | ssoStore.clearTokenLaunchpad(), authStore.resetAuth(), SSO_URL/sign_out |
switch_user | session exists, different user_sso_id | clear tokens → re-auth via authz-code flow → on return, sync company → "account changed" toast | authenticated.global.ts:80 redirect builder, toast.notify |
server_down | SDK exhausted backoff | msli fallback predicate; on fail → product sign-out | localStorage msli — Q2 |
State Surface Contract
| Entity | Surfaced to | Source of truth | Notes |
|---|---|---|---|
| Session status | route middleware + auth store | SDK event | drives gate/redirect |
user_sso_id | SDK input + comparison | launchpad.sso_id cookie | set at authStore.ts:139 |
msli timestamp | server_down fallback | localStorage | fallback only |
2.3.1 ADR-1 — Auth model variant: OAuth2 Authorization Code
- Context: PRD defines two product variants (Rails web-session vs OAuth2 authz-code). Launchpad authenticates via SSO authorization-code exchange.
- Options: (a) web-session flow with
client_credentials+ Rails cookie; (b) OAuth2 authz-code flow with bearer tokens. - Decision: (b). Launchpad already exchanges
authorization_code(ssoCallbackStore.ts:53) and stores bearer tokens inglobal_sso_*cookies. - Rationale: matches existing code; no Rails session in this SPA.
- Consequences:
logged_out/switch_user/server_downmust revoke tokens, not destroy a Rails session. Current-company uses the/meendpoint variant. - Reversibility: high — variant is isolated to the event handlers.
2.3.2 ADR-2 — SDK load location: client plugin
- Context: SPA (
ssr:false); SDK injects an iframe and needs DOM. - Options: (a)
app.vueuseHeadscript; (b)*.client.tsNuxt plugin; (c) per-page import. - Decision: (b)
app/plugins/mekariSession.client.ts. - Rationale: plugins already wire global singletons (
plugins/auth.ts); client-only suffix guarantees browser context; single init point. - Consequences: SDK runs on every authenticated page; init must be idempotent and toggle-gated.
- Reversibility: high — delete plugin / flip toggle.
2.3.3 ADR-3 — Toggle mechanism
- Context: PRD requires a
centralized_sessiontoggle; Launchpad's only toggle (useToggleQontakOne.ts:11) is hardcodedalways-on. - Options: (a) hardcoded const; (b) runtimeConfig env var; (c) backend per-company endpoint.
- Decision: mirror
useToggleQontakOnestructure with aTOGGLE_SOURCEswitch; default source unresolved — Q4 (env var preferred for the pilot). - Rationale: consistency with the existing pattern; isolates the rollout lever.
- Consequences: if per-company control is required at step 5, source flips
to backend (same as the documented
useToggleQontakOnebackend path). - Reversibility: high.
2.3.4 ADR-4 — Event wiring point
- Context: events must end/redirect sessions; the auth gate is route middleware.
- Options: (a) handle entirely in plugin; (b) plugin emits → composable → middleware + store.
- Decision: (b). Plugin forwards events to
useCentralizedSession; the middleware awaits the initial resolution (PRD: "Product FE waits for SDK response" when toggle on), store actions perform token/company changes. - Rationale: reuses the existing single gate (
authenticated.global.ts) and avoids duplicate redirect logic. - Consequences: middleware gains a toggle-gated await; must not block when toggle off.
- Reversibility: medium — touches the shared middleware.
ADR minimum-coverage: Storage n/a — no DB (FE); Sync/async — SDK event +
company fetch are async, awaited in middleware; Caching — Session Manager
disables caching (max 5s, upstream); msli is the FE fallback cache (Q2);
Third-party — SDK via iframe/postMessage (ADR-2); Consistency — eventual
(SDK is authoritative, msli is best-effort fallback); Multi-tenancy — company
sync per user_sso_id; Reuse/new — reuses sign-out + re-auth builders, new
composables only.
2.4 Sequence diagrams
Happy path — logged_in
sequenceDiagram
participant U as User
participant FE as Launchpad FE
participant MW as authenticated.global.ts
participant SDK as @mekari/sdk
participant IF as iframe (account.mekari.com/sm/current)
participant SM as Session Manager + Redis
participant API as SSO current_company
U->>FE: open page
FE->>MW: route enter
MW->>SDK: (toggle on) await session
SDK->>IF: open iframe (_mekari_account cookie)
IF->>SM: GET /sm/current
SM->>SM: validate Redis, update last_request_at
SM-->>IF: render user_sso_id (no-cache ≤5s)
IF-->>SDK: postMessage(user_sso_id)
SDK-->>MW: event logged_in
MW->>FE: set msli=now, allow nav
FE->>API: GET /users/me/current_company (Q3)
API-->>FE: current company
FE-->>U: render
Failure path — server_down (timing: SDK backoff exhausted)
sequenceDiagram
participant FE as Launchpad FE
participant SDK as @mekari/sdk
participant SM as Session Manager
SDK->>SM: GET /sm/current (retry-backoff)
SM--xSDK: timeout / unreachable
SDK-->>FE: event server_down
Note over FE: fallback predicate (Q2)
alt msli < 2h AND _mekari_account valid [UNREADABLE cross-domain — Q2]
FE->>FE: treat as logged_in
else stale / invalid
FE->>FE: revoke tokens → product sign-out
end
switch_user re-auth
sequenceDiagram
participant U as User
participant FE as Launchpad FE
participant SDK as @mekari/sdk
participant SSO as SSO auth
SDK-->>FE: event switch_user (different user_sso_id)
FE->>FE: clearTokenLaunchpad() + resetAuth() + remove msli
FE->>SSO: redirect SSO_URL/auth?client_id=&response_type=code&redirect_uri=BASE_URL/sso-callback
SSO-->>FE: autologin (valid _mekari_account) → /sso-callback?code
FE->>FE: fetchOauthSSO(code) → fetchAuthLaunchpad → sync company
FE-->>U: home + "your account has changed" toast
logged_out / product logout
sequenceDiagram
participant U as User
participant FE as Launchpad FE
participant SDK as @mekari/sdk
participant SSO as SSO sign_out
SDK-->>FE: event logged_out
FE->>FE: clearTokenLaunchpad() + resetAuth() (cookies cleared)
FE->>SSO: window.location SSO_URL/sign_out?client_id=
SSO-->>U: empty-session redirect to sign in
2.5 Current-company sync
Per PRD OAuth2 variant, after a session is confirmed Launchpad must fetch the
current company. Launchpad's existing profile call goes through Kong
(authStore.ts:80 → apiBaseUrl/users/me). The current-company endpoint
(/users/me/current_company) is not present in the repo and has no verified
FE-reachable host. New composable useCurrentCompany.ts will call it via
useClient, but the endpoint owner/host is an Open Question (Q3) — do not
hardcode api.mekari.com from the FE without confirming CORS/Kong routing.
Endpoint tag: new-with-justification — no existing Launchpad contract
returns current company; reuse of /users/me is insufficient because the PRD
requires a post-switch re-sync against SSO's authoritative current company.
APIs — Outbound (FE → BE)
| Method | Path | Status | Notes |
|---|---|---|---|
| GET | /users/me/current_company (via Kong or BE proxy) | new-with-justification | host/owner unresolved — Q3 |
| POST | /seamless/refresh_sso_token | reused | useClient.ts:119 |
| GET | /users/me | reused | authStore.ts:80 |
| redirect | SSO_URL/sign_out?client_id= | reused | SwitchAccountContent.vue:117 |
| redirect | SSO_URL/auth?...response_type=code | reused | authenticated.global.ts:80 |
APIs — Inbound (→ Launchpad) — n/a — FE has no inbound HTTP; SDK delivers events via postMessage
Role × Endpoint Authorization — n/a — session handling is role-agnostic; all authenticated users follow the same flow
Branch & Skip Catalog
| Branch | Trigger | Owner |
|---|---|---|
| Toggle off → skip SDK | centralized_session false | FE |
msli fallback skip sign-out | valid msli on server_down | FE (Q2) |
| Excluded pages (login/sso-callback) → no gate | authenticated.global.ts:11 | FE |
2.6 Database Model
n/a — no database changes (frontend RFC).
3. High-Availability & Security
3.1 Performance (FE-relevant)
Service-side RPS/latency (6k/s, 50ms avg, p95 < 100ms) are owned by the Session
Manager service. FE obligation: SDK init must be non-blocking when the
toggle is off, and the awaited resolution when on must have a hard client-side
timeout so a slow /sm/current cannot hang Launchpad navigation indefinitely
(falls through to server_down per ADR-4).
3.2 Security (OWASP-aligned)
- A05 / A07 — SDK loading hardening (PRD §5 open): decide CSP
frame-ancestorswhitelist vs referrer/origin validation. Adding the Launchpad origin to the SDK whitelist is the step-5 rollout gate. FE-side: confirm any Launchpad CSP header does not break theaccount.mekari.comiframe — Q6. - A02 — token handling:
logged_out/switch_usermust clearglobal_sso_*cookies via existingclearTokenLaunchpad/resetAuth; never log tokens (AGENTS.md). - A01 —
_mekari_accountis cross-origin to Launchpad; the FE cannot read it. Any fallback predicate depending on it is unenforceable from the product page — Q2 (critical).
3.3 Observability
Reuse console.error/console.warn for unexpected SDK states (AGENTS.md;
no console.log in committed code). Route any API errors through
useErrorHandler. Add an FE counter/log line on server_down fallback hits so
the pilot can measure SDK reachability. [REQUIRED: confirm FE telemetry sink — Mixpanel via plugins/mixpanel.ts or log only].
4. Backwards Compatibility & Rollout Plan
4.A Compatibility
Toggle-gated; when centralized_session is off, behavior is byte-for-byte the
current flow (SDK never instantiated, middleware await skipped). Logout already
hits SSO sign_out (SwitchAccountContent.vue:117), so no regression there.
4.B Pre-merge verification commands
| Step | Command | Source |
|---|---|---|
| Lint | pnpm run lint | package.json "lint" |
| Typecheck | pnpm run type-check | package.json "type-check" |
| Test | pnpm run test | package.json "test": "vitest --dom --pool=forks" |
| Build | pnpm run build | package.json "build": "nuxt build" |
Install caveat (AGENTS.md):
qontak-unified-componentis agit+sshBitbucket dep;pnpm installneeds SSH access. Do not loop on install failures.
4.C Agent Execution Plan
Chunk 1 — centralized_session toggle
- Files:
app/common/composables/useCentralizedSessionToggle.ts(new),app/common/composables/useCentralizedSessionToggle.spec.ts(new). - Command:
pnpm run test -- app/common/composables/useCentralizedSessionToggle.spec.ts - Accept: toggle off → composable returns
false; on →true. Test red first. - Blocked by: Q4 (source).
Chunk 2 — SDK loader plugin
- Files:
app/plugins/mekariSession.client.ts(new), spec. - Accept: when toggle on and
launchpad.sso_idset,new Session({current_user})is called with that id (mock SDK); when toggle off, SDK is not loaded. - Blocked by: Q1 (distribution), Q5 (iframe URL).
Chunk 3 — logged_out handler
- Files:
app/common/composables/useCentralizedSession.ts(new) + spec. - Accept:
logged_out→clearTokenLaunchpad()+resetAuth()called + redirect toSSO_URL/sign_out?client_id=. ReusessoCallbackStore.
Chunk 4 — logged_in + current-company sync
- Files:
app/common/composables/useCurrentCompany.ts(new) + spec; extenduseCentralizedSession.ts. - Accept:
logged_in→msliwritten, company fetched viauseClient, set on store. Blocked by: Q3 (endpoint host).
Chunk 5 — switch_user re-auth + toast
- Files: extend
useCentralizedSession.ts; modifyauthenticated.global.ts. - Accept:
switch_user→ tokens cleared → redirect to authz-code URL → on/sso-callbackreturn,toast.notify"account changed" shown.
Chunk 6 — server_down fallback
- Files: extend
useCentralizedSession.ts+mslihelper + spec. - Accept: valid
msli(<2h) → treat logged_in; else sign-out. - Blocked by Q2 —
_mekari_accountpart of the predicate is unreadable cross-domain; needs a redefined FE-only predicate before implementation.
Chunk 7 — middleware integration
- Files:
app/middleware/authenticated.global.ts(modify). - Accept: toggle on → middleware awaits initial SDK resolution with a timeout; toggle off → unchanged path (existing specs still green).
Order respects dependencies: toggle → loader → handlers → middleware.
4.D Verification & Rollback Recipe
- Pre-merge: run §4.B in order; all green.
- Post-deploy signals:
server_downfallback counter near zero; no spike in unexpected sign-outs; SDK iframe loads on Launchpad (network panel showsaccount.mekari.com/sm/current).[REQUIRED: dashboard/metric name]. - Rollback: set
centralized_sessiontoggle off (one lever — ADR-3) → behavior reverts to current flow; if toggle is env-sourced, revert the env value and redeploy; revert the SDK-loader PR if the plugin itself misbehaves.
4.E Rollout (Launchpad = PRD step 4 pilot)
| Stage | Audience | Go/No-go evidence |
|---|---|---|
| Internal | Account & Launchpad team | events fire correctly in staging |
| Canary | toggle on for pilot company set | no abnormal sign-out rate; fallback counter low |
| GA | all Launchpad users | canary signals stable; infosec sign-off on SDK hardening |
5. Concern / Questions / Known Limitations
- Q1 [critical] SDK distribution: npm
@mekari/sdk(not inpackage.json) vs CDNaccount.mekari.com/sm/sdk.js? Blocks chunk 2. Owner: Account/SSO + AL. - Q2 [critical]
server_downfallback predicate references_mekari_accountvalidity, but that cookie isaccount.mekari.com-scoped and unreadable from the Launchpad origin (useAuthCookies.tshas no such cookie). Predicate must be redefined as FE-readable (msli+global_sso_valid_until?). Owner: SSO + AL. - Q3 [critical] Current-company endpoint host/owner: is there a Launchpad BE
proxy, or does FE call
api.mekari.comdirectly (CORS/Kong)? Blocks chunk 4. Owner: Launchpad BE. - Q4 [important]
centralized_sessiontoggle source for the pilot (env var vs backend per-company). Owner: AL. - Q5 [important] Canonical Session Manager path:
/sm/currentvs/sessionmanager/current(PRD inconsistent). Owner: Account/SSO. - Q6 [important] Does any Launchpad CSP break the SDK iframe; which hardening
(CSP
frame-ancestorsvs referrer/origin) applies to the FE? Owner: infosec. - [nice-to-have] Whether a visible loading state is needed while the SDK resolves. Owner: design.
6. Comment logs
| Date | Author | Comment |
|---|---|---|
| 2026-06-28 | rfc-starter | Initial draft from PRD; 4 critical FE/cross-layer blockers raised. |
7. Ready for agent execution
Ready for agent execution: no
Blocking gates (must resolve before yes):
- Q1 SDK distribution (blocks SDK loader).
- Q2
server_downfallback predicate (cross-domain cookie conflict). - Q3 current-company endpoint host/owner (cross-layer dependency).
Non-blocking but required for full coverage: Q4 (toggle source), Q5 (iframe path), Q6 (CSP). Optional: design loading state.
Optional second pass: hand to rfc-reviewer for scoring once Q1–Q3 close.