Thanks for the revision. I don’t think the generic layer is needed, though, because frontend-base already has most of it:
The initialization sequence publishes lifecycle events (SITE_CONFIG_INITIALIZED, SITE_AUTH_INITIALIZED, SITE_READY, AUTHENTICATED_USER_CHANGED) that any module can subscribe to. That covers onAppInit and onUserSessionStart. initialize() also accepts an authMiddleware array (ADR 0006) that is applied to every HTTP client the auth service creates, including the cached ones, which covers both HTTP client hooks. And the App interface has a per-app externalScripts field (ADR 0014) that is exactly the “run some code at boot with this app’s config” shape; consent banners were the motivating example in that ADR. Section 2 compares against frontend-plugin-framework’s PluginSlot, but frontend-base has its own slot system plus these non-UI hooks, so the claim that there is no extension point for any of this doesn’t hold.
onLogout has no frontend counterpart to hook. Logout is still just a redirect to the LMS logout URL, so there is no client-side step where the hook could fire. The server-side cookie deletion you already propose is the right place for that. It does edit the logout view, though, which should be reconciled with the “no existing LMS code is modified” claim.
The one thing missing is that authMiddleware is only reachable as an initialize() option, and the shell calls initialize() with just messages, so operators can’t set it from site.config.tsx. That is the frontend-base change I’d propose instead: a middleware field on App, so that multiple apps compose rather than clobber each other, following the same reasoning that led ADR 0014 to reject a site-level array.
With that in place, UTM attribution becomes an ordinary App in its own package: externalScripts for the URL capture and cookie write, the new middleware field for the header interceptor, and App.config for cookie name and domain. Operators add it to apps in site.config.tsx like any other app. No new manager, no new plugin type, and the framework-level part of the RFC shrinks to the middleware field.
A few smaller things to adjust when reworking: site config keys are camelCase (lmsBaseUrl) and there is no cookie domain key, so the app needs to carry that in its own config; IDs use reverse-domain notation (org.openedx.frontend.app.*); and the rollout should be framed around a single shell rather than independently upgraded MFEs.
If the next version comes as a PR that adds an ADR to frontend-base instead of a Word document, that would be ideal!