Skip to main content

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-session Golang 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

FieldValueNotes
StatusRFCIDEA / RFC / ABANDON / AGREED
TypefrontendLaunchpad is a Nuxt 4 SPA (ssr: false)
Sub-typenew-featurenew SDK integration + session-event handling
TitleCentralized Web Session — Launchpad FE Integration
OwnerAccount & Launchpad
AuthorsAccount & Launchpad team
ReviewersSSO/Session service reviewer; Launchpad BE reviewerplaceholders pending
ApproversLaunchpad tech lead; infosecinfosec approval required
Submitted2026-06-28ISO-8601
Last updated2026-06-28matches latest edit
Target releaseTBDplaceholder — see Open Questions
Related documentsCentralized Web Session PRDauthoritative driver
DiscussionTBD

Sections at a Glance

§SectionFE hints
1Overviewgoals, scope, Design References, PRD coverage, Decisions index, Per-Story Change Map
2Technical DesignRepo Reading Guide, SDK contract, event handling, current-company sync, diagrams
3High-Availability & SecuritySDK loading hardening, FE observability, perf expectations
4Backwards Compatibility & Rollouttoggle rollout, Agent Execution Plan, Verification & Rollback Recipe
5Concern / Questions / Known LimitationsOpen Questions with severity
6Comment logsreview trail
7Ready for agent executionthe 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_session feature toggle.

1.2 Non-goals

  • Building the mekari-session Golang 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).

SurfaceFigma frameImplementing fileNotes
"Account has changed" notificationn/a — reuse existing Pixel toast, no new Figmaapp/common/store/authStore.ts:117 patterntext-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.tsPRD: "Product FE waits for SDK response" when toggle on

1.A PRD Coverage

PRD Section Coverage

PRD sectionCovered inStatus
1. Overview / known issues§1.0, §1.1covered
Success Criteria§1.1, §2.xcovered (FE portion)
Out of Scope§1.2covered
Dependencies (SDK, service, Redis)§2.1 upstream contractscovered (upstream, read-only)
2. Technical Design — Proposal§2.2–§2.6covered (FE portion)
SDK contract / events§2.3covered
FE Product Integration Flows§2.4–§2.6covered
Web Session Flow§2.5n/a — Launchpad uses OAuth2 authz-code flow, not Rails web session
OAuth2 Authorization Code Flow§2.5covered — Launchpad's actual auth model
User Logout From Product§2.6covered
User Switch Account§2.5 switch_usercovered
Database Modeln/a — no DB changes (FE)
3. HA & Security§3covered (FE-relevant subset)
4. Rollout Plan§4covered (Launchpad step 4)
5. Open Questions§5covered
FE Implementation Scope§1.C, §4covered

UI / Consumer Surface Coverage

SurfaceRead endpoint / sourceCoverage
"Account changed" toastSDK switch_user event§2.4
Session gateSDK init resolution§2.4

Role Coverage

RoleRelevanceCoverage
Any authenticated Launchpad userAll session events apply uniformly§2.4
no per-role session behavior in PRDn/a — session handling is role-agnostic

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

#DecisionChosenADR
D1Auth model variant for LaunchpadOAuth2 Authorization Code flow (not web-session)ADR-1
D2Where to load the SDK*.client.ts Nuxt pluginADR-2
D3Toggle mechanismreuse useToggleQontakOne pattern, new centralized_session toggleADR-3
D4Session event → app reaction wiring pointroute middleware + auth storeADR-4
D5SDK distribution (npm vs CDN script)unresolved — see Open Question Q1

1.C Per-Story Change Map

Story (PRD)Layer scopeChangesAcceptance criteriaRFC anchors
Load SDK with user_sso_id on every pageFE-onlyapp/plugins/mekariSession.client.ts (new), reads authProfile.sso_idplugin instantiates Session({current_user}); unit test asserts SDK constructed with cookie launchpad.sso_id value§2.2, §4.C chunk 2
Gate behind centralized_sessionFE-onlyuseCentralizedSessionToggle.ts (new)toggle off → no SDK side effects (test)§2.3 ADR-3, §4.C chunk 1
Handle logged_inFE + BEsync current company after session confirmedon logged_in, current company fetched & set; test stubs SDK event§2.4, §2.5, §4.C chunk 4
Handle logged_outFE-onlyrevoke tokens + product sign-out (OAuth2 variant)event → resetAuth() + cookies cleared + redirect to SSO sign_out§2.4, §4.C chunk 3
Handle switch_userFE (+ BE for company)re-auth via existing OAuth flow + "account changed" toastevent → tokens cleared → SSO_URL/auth?... re-auth → toast shown§2.4, §2.5, §4.C chunk 5
Handle server_downFE-onlymsli fallback then product sign-outfallback predicate evaluated; sign-out on exhaustion§2.4, §4.C chunk 6 — blocked by Q2
Wire product logout → SSO sign_outFE-onlyalready presentlogout hits SSO_URL/sign_out?client_id=§2.6, SwitchAccountContent.vue:117 (reuse)
msli local-storage fallbackFE-onlynew helpermsli written on logged_in, read on server_down§2.4 — blocked by Q2
Current-company syncFE + BEnew composable + BE endpointcompany 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

FileWhat to learn
app/middleware/authenticated.global.tsthe single auth gate; existing token-refresh + unified-logout flow; where to await SDK
app/common/store/authStore.tsfetchAuthLaunchpad, setProfileAuth (sets LAUNCHPAD_SSO_ID), resetAuth; authProfile.sso_id is the user SSO ID
app/common/store/ssoCallbackStore.tssetTokenLaunchpad/clearTokenLaunchpad, OAuth2 fetchOauthSSO (authorization_code)
app/common/composables/useAuthCookies.tscookie 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.tscentral $fetch wrapper, 401 refresh singleton, handleAuthenticationFailure
app/common/composables/useToggleQontakOne.tsexisting feature-toggle pattern to mirror for centralized_session
app/features/sso-callback/composable/ssoCallback.tshandleRedirection; SSO authz URL construction (SSO_URL/auth?client_id=...&response_type=code)
app/layouts/components/SwitchAccountContent.vuesign-out wiring → SSO_URL/sign_out?client_id= (line 117) — already matches PRD §2.6
app/app.vue / app/layouts/default.vuemount points; $auth provided by plugins/auth.ts

Patterns to Follow

ConcernReference filePattern
State managementapp/common/store/authStore.tsPinia setup store, refs + actions
Feature toggleapp/common/composables/useToggleQontakOne.ts:11module-singleton TOGGLE_SOURCE switch
API callapp/common/composables/useClient.ts:43useClient(url, opts){data,error}; read body at data.value.data
Auth cookiesapp/common/composables/useAuthCookies.ts:9useCookie with prod domain from config
Error normalizeapp/common/composables/useErrorHandler.tsroute API errors through useErrorHandler (per AGENTS.md)
User notificationapp/common/store/authStore.ts:117toast.notify({position,variant,title})
SSO re-auth redirectapp/middleware/authenticated.global.ts:79SSO_URL/auth/?client_id=&response_type=code&scope=sso:profile&redirect_uri=BASE_URL/sso-callback
Logout redirectapp/layouts/components/SwitchAccountContent.vue:117clear cookies → SSO_URL/sign_out?client_id=
Composable testapp/common/composables/useAuthCookies.spec.tsVitest + happy-dom, mock #app

Reading Order for the Agent

  1. app/middleware/authenticated.global.ts
  2. app/common/store/authStore.ts
  3. app/common/store/ssoCallbackStore.ts
  4. app/common/composables/useAuthCookies.ts
  5. app/common/composables/useClient.ts
  6. app/common/composables/useToggleQontakOne.ts
  7. app/features/sso-callback/composable/ssoCallback.ts
  8. app/layouts/components/SwitchAccountContent.vue
  9. nuxt.config.ts (runtimeConfig: SSO_URL, SSO_UNIFIED_CLIENT_ID, CHATPANEL_URL, BASE_URL, COOKIE_DOMAIN, apiBaseUrl)
  10. app/common/composables/useAuthCookies.spec.ts (test conventions)

2.0.1 Source Verification

ItemEvidence
Auth gate is route middlewareapp/middleware/authenticated.global.ts:5 defineNuxtRouteMiddleware; refresh at :44, unified logout at :90
User SSO ID sourceauthStore.ts:139 LAUNCHPAD_SSO_ID.value = payload?.sso_id; cookie launchpad.sso_id at useAuthCookies.ts:30
OAuth2 authz-code is Launchpad's modelssoCallbackStore.ts:53 grantType: "authorization_code"; redirect builder authenticated.global.ts:80
Token cookiesuseAuthCookies.ts:5-7 global_sso_token/global_sso_refresh_token/global_sso_valid_until
Sign-out already hits SSOSwitchAccountContent.vue:117 window.location.href = ${ssoBaseUrl}/sign_out?client_id=${ssoClientId}
Toggle patternuseToggleQontakOne.ts:11 const TOGGLE_SOURCE = "always-on"
Central fetch wrapperuseClient.ts:9; 401 refresh :51; handleAuthenticationFailure :146
Test stackpackage.json: "test": "vitest --dom --pool=forks"; example spec useAuthCookies.spec.ts
_mekari_account NOT a Launchpad cookiegrep of useAuthCookies.ts — only global_sso_* + launchpad.sso_id; no _mekari_accountconflict, see Q2
@mekari/sdk NOT installedpackage.json dependencies — absent → see Q1
current_company sync absentgrep current_company across **/*.{ts,vue} → no matches → new, see Q3
centralized_session toggle absentgrep centralized_session → no matches → new

2.1 Upstream contracts (read-only, owned by SSO/Session squads)

ContractShapeOwner
SDK package @mekari/sdknew Session({current_user}), .on("event", (data,error)=>{}), .refresh()Account/SSO
SDK CDNhttps://account.mekari.com/sm/sdk.js (CJS+ESM)Account/SSO
Session Manageriframe → account.mekari.com/sm/current (PRD also says /sessionmanager/current — Q5)Account/SSO
Eventslogged_in, logged_out, switch_user, server_downAccount/SSO
Current companyGET 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)

EventMeaningLaunchpad reaction (OAuth2 authz-code)Reuses
logged_insession exists, same user_sso_idwrite msli=now; sync current company; allow interactionuseClient, current-company composable
logged_outno session (logout or 2h idle)revoke tokens → product sign-outssoStore.clearTokenLaunchpad(), authStore.resetAuth(), SSO_URL/sign_out
switch_usersession exists, different user_sso_idclear tokens → re-auth via authz-code flow → on return, sync company → "account changed" toastauthenticated.global.ts:80 redirect builder, toast.notify
server_downSDK exhausted backoffmsli fallback predicate; on fail → product sign-outlocalStorage msliQ2

State Surface Contract

EntitySurfaced toSource of truthNotes
Session statusroute middleware + auth storeSDK eventdrives gate/redirect
user_sso_idSDK input + comparisonlaunchpad.sso_id cookieset at authStore.ts:139
msli timestampserver_down fallbacklocalStoragefallback 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 in global_sso_* cookies.
  • Rationale: matches existing code; no Rails session in this SPA.
  • Consequences: logged_out/switch_user/server_down must revoke tokens, not destroy a Rails session. Current-company uses the /me endpoint 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.vue useHead script; (b) *.client.ts Nuxt 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_session toggle; Launchpad's only toggle (useToggleQontakOne.ts:11) is hardcoded always-on.
  • Options: (a) hardcoded const; (b) runtimeConfig env var; (c) backend per-company endpoint.
  • Decision: mirror useToggleQontakOne structure with a TOGGLE_SOURCE switch; 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 useToggleQontakOne backend 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:80apiBaseUrl/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)

MethodPathStatusNotes
GET/users/me/current_company (via Kong or BE proxy)new-with-justificationhost/owner unresolved — Q3
POST/seamless/refresh_sso_tokenreuseduseClient.ts:119
GET/users/mereusedauthStore.ts:80
redirectSSO_URL/sign_out?client_id=reusedSwitchAccountContent.vue:117
redirectSSO_URL/auth?...response_type=codereusedauthenticated.global.ts:80

APIs — Inbound (→ Launchpad)n/a — FE has no inbound HTTP; SDK delivers events via postMessage

Role × Endpoint Authorizationn/a — session handling is role-agnostic; all authenticated users follow the same flow

Branch & Skip Catalog

BranchTriggerOwner
Toggle off → skip SDKcentralized_session falseFE
msli fallback skip sign-outvalid msli on server_downFE (Q2)
Excluded pages (login/sso-callback) → no gateauthenticated.global.ts:11FE

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-ancestors whitelist 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 the account.mekari.com iframe — Q6.
  • A02 — token handling: logged_out/switch_user must clear global_sso_* cookies via existing clearTokenLaunchpad/resetAuth; never log tokens (AGENTS.md).
  • A01 — _mekari_account is 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

StepCommandSource
Lintpnpm run lintpackage.json "lint"
Typecheckpnpm run type-checkpackage.json "type-check"
Testpnpm run testpackage.json "test": "vitest --dom --pool=forks"
Buildpnpm run buildpackage.json "build": "nuxt build"

Install caveat (AGENTS.md): qontak-unified-component is a git+ssh Bitbucket dep; pnpm install needs 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_id set, 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_outclearTokenLaunchpad() + resetAuth() called + redirect to SSO_URL/sign_out?client_id=. Reuse ssoCallbackStore.

Chunk 4 — logged_in + current-company sync

  • Files: app/common/composables/useCurrentCompany.ts (new) + spec; extend useCentralizedSession.ts.
  • Accept: logged_inmsli written, company fetched via useClient, set on store. Blocked by: Q3 (endpoint host).

Chunk 5 — switch_user re-auth + toast

  • Files: extend useCentralizedSession.ts; modify authenticated.global.ts.
  • Accept: switch_user → tokens cleared → redirect to authz-code URL → on /sso-callback return, toast.notify "account changed" shown.

Chunk 6 — server_down fallback

  • Files: extend useCentralizedSession.ts + msli helper + spec.
  • Accept: valid msli (<2h) → treat logged_in; else sign-out.
  • Blocked by Q2_mekari_account part 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_down fallback counter near zero; no spike in unexpected sign-outs; SDK iframe loads on Launchpad (network panel shows account.mekari.com/sm/current). [REQUIRED: dashboard/metric name].
  • Rollback: set centralized_session toggle 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)

StageAudienceGo/No-go evidence
InternalAccount & Launchpad teamevents fire correctly in staging
Canarytoggle on for pilot company setno abnormal sign-out rate; fallback counter low
GAall Launchpad userscanary signals stable; infosec sign-off on SDK hardening

5. Concern / Questions / Known Limitations

  • Q1 [critical] SDK distribution: npm @mekari/sdk (not in package.json) vs CDN account.mekari.com/sm/sdk.js? Blocks chunk 2. Owner: Account/SSO + AL.
  • Q2 [critical] server_down fallback predicate references _mekari_account validity, but that cookie is account.mekari.com-scoped and unreadable from the Launchpad origin (useAuthCookies.ts has 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.com directly (CORS/Kong)? Blocks chunk 4. Owner: Launchpad BE.
  • Q4 [important] centralized_session toggle source for the pilot (env var vs backend per-company). Owner: AL.
  • Q5 [important] Canonical Session Manager path: /sm/current vs /sessionmanager/current (PRD inconsistent). Owner: Account/SSO.
  • Q6 [important] Does any Launchpad CSP break the SDK iframe; which hardening (CSP frame-ancestors vs 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

DateAuthorComment
2026-06-28rfc-starterInitial 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_down fallback 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.