Skip to content

Activate one auth provider instead of failing the boot on SM020 - #256

Merged
antosubash merged 4 commits into
mainfrom
claude/project-build-compile-jlnnya
Aug 9, 2026
Merged

Activate one auth provider instead of failing the boot on SM020#256
antosubash merged 4 commits into
mainfrom
claude/project-build-compile-jlnnya

Conversation

@antosubash

@antosubash antosubash commented Aug 9, 2026

Copy link
Copy Markdown
Owner

Problem

A fresh clone can't boot. uv sync --all-packages installs every workspace member, so both users and keycloak register auth-provider entry points, SM020 fires as an error, and create_app exits before serving a request:

✗ SM020 [ERROR] Keycloak: Multiple auth provider modules installed: Keycloak, Users
Module diagnostics: 1 error(s). Fix before continuing.

make dev, importing host.main, and make doctor all died on a clean checkout. The workaround was an explicit SM_MODULES_ENABLED allowlist, which nothing in .env.example or the README mentioned — and framework/hosting/tests/test_app.py carried the same workaround inline.

Change

SM_AUTH_PROVIDER (default users) plus select_auth_provider(), which drops the non-selected providers at discovery when more than one is installed. Keycloak stays installed and testable but inert until asked for.

Behaviour at the edges:

  • Fewer than two providers installed → nothing dropped, so a keycloak-only host keeps keycloak whatever the setting says.
  • The configured name matches none of them (typo) → every provider stays mounted, which means two modules write app.state.auth.auth_provider and topological order silently picks the winner. So this always logs a warning, and select_auth_provider(..., strict=True) raises InvalidModuleError. create_app passes strict=not is_development, because run_diagnostics is gated on is_development — SM020 would never report this in a deployed app.
  • Blank SM_AUTH_PROVIDER= → normalised to the default by a field validator, so the host agrees with resolve_auth_provider() (used by make doctor / gen-pages) instead of diverging on ''.

Applied in create_app, make doctor, and smpy host gen-pages so the pages manifest, the diagnostics, and the running app agree on one module set.

Test isolation

The README tells Keycloak users to put SM_AUTH_PROVIDER=keycloak in .env — and Settings reads .env, so that config reshaped the whole suite (152 failures). Pinning the shared settings fixture wasn't enough: module conftests build their own Settings. A session-scoped autouse fixture in simple_module_test pins the env var itself, which outranks .env and so covers every construction site at once. The suite is now green under both configurations.

Incidental fixes

  • users and background_tasks call register_module_settings, which reads app.state.settings, but neither declared a dependency on Settings — they were ordered correctly only via a transitive edge through another module. Selecting keycloak removed that edge and background_tasks crashed at boot with AttributeError: 'State' object has no attribute 'settings'. Both now declare the dependency they actually have.
  • Menu icons rendering as blank spacers. Branding (palette), Audit Log (scroll-text), and the Doctor page (stethoscope) all declared icons with no ICON_MAP entry in NavIcon.tsx. NavIcon falls back to an empty <span className="w-5 h-5"> for an unknown name rather than throwing, so nothing surfaced it — no console warning, no failing test. host/tests/test_nav_icons.py now walks every module's register_menu_items() and asserts each declared icon is mapped; it found stethoscope, which a manual grep had missed.
  • The one Biome warning in the tree (useOptionalChain in ModuleForm.tsx).

Docs

New README section under User management stating that users and keycloak are mutually exclusive, how to switch, and two caveats: dashboard/permissions/audit_log/background_tasks depend on the Users module so simple_module_users must stay installed, and the local-account flows disappear while Keycloak is active. SM_AUTH_PROVIDER added to the config table and to .env.example.

Verification

Check Result
uv run pytest 1785 passed, 2 skipped
uv run pytest with SM_AUTH_PROVIDER=keycloak in .env 1785 passed (was 152 failures)
npm test 48 passed
make lint clean (Ruff, ty, Biome, tsc, file-size, metadata)
make build clean
make doctor 0 errors (was 1)
Boot with no env set 30 routes, 11 modules
Boot with SM_AUTH_PROVIDER=keycloak 30 routes
Boot in production with a typo'd provider raises InvalidModuleError naming the value and the installed providers

New tests: framework/core/tests/test_auth_provider_selection.py (selection, case-insensitive match, both no-op paths, the warn and strict-raise paths, .env resolution precedence), framework/hosting/tests/test_auth_provider_setting.py (blank/whitespace normalisation, and that Settings and resolve_auth_provider() agree on every input), and host/tests/test_nav_icons.py.

Also driven end-to-end in a browser: logged in as a bootstrapped admin and walked all eight sidebar pages with no 4xx/5xx and no console errors.

Not addressed

The dashboard home page renders raw i18n keys (dashboard.home.title, DASHBOARD.HOME.STATS.TOTAL_USERS) even though modules/dashboard/dashboard/locales/en.json defines them. Confirmed pre-existing by screenshotting the same page on the parent commit — out of scope here, worth its own issue.

https://claude.ai/code/session_01RtK7xkMUveHaw6TdK1DF7L

A fresh clone could not boot. `uv sync --all-packages` installs every
workspace member, so both `users` and `keycloak` register auth-provider
entry points, SM020 fires, and `create_app` exits before serving a
request — `make dev`, `python -m host.main`, and `make doctor` all died on
a clean checkout.

Add `SM_AUTH_PROVIDER` (default `users`) and `select_auth_provider()`,
which drops the non-selected providers at discovery when more than one is
installed. Keycloak stays installed and testable but inert until asked
for. Nothing is dropped when only one provider is present, so a
keycloak-only host is unaffected, and an unrecognised name leaves the
conflict for SM020 to report rather than silently picking a winner.

Applied in `create_app`, `make doctor`, and `smpy host gen-pages` so the
manifest, the diagnostics, and the running app agree on the module set.

Also:

- `users` and `background_tasks` call `register_module_settings`, which
  reads `app.state.settings`, but neither declared a dependency on
  `Settings` — they were ordered correctly only by way of a transitive
  edge. Selecting keycloak removed that edge and `background_tasks`
  crashed at boot. Declare the dependency they actually have.
- Fix the one Biome warning (`useOptionalChain` in ModuleForm.tsx).

Claude-Session: https://claude.ai/code/session_01RtK7xkMUveHaw6TdK1DF7L
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 9, 2026

Copy link
Copy Markdown

Deploying simple-module-python with  Cloudflare Pages  Cloudflare Pages

Latest commit: 7ed4ddc
Status: ✅  Deploy successful!
Preview URL: https://7e820b2a.simple-module-python.pages.dev
Branch Preview URL: https://claude-project-build-compile.simple-module-python.pages.dev

View logs

claude added 2 commits August 9, 2026 14:07
Branding declares `palette`, Audit Log declares `scroll-text`, and the
Doctor page declares `stethoscope` — none were in NavIcon's ICON_MAP, so
those three sidebar entries rendered an empty 5x5 span where the icon
should be. NavIcon falls back to that spacer for an unknown name instead
of throwing, so nothing surfaced the gap: no console warning, no failing
test, just a missing icon.

Add the three lucide imports and their map entries.

Guard it with host/tests/test_nav_icons.py, which walks every module's
register_menu_items(), collects the declared icon names, and asserts each
one has an ICON_MAP entry. It found `stethoscope`, which a manual grep of
the module sources had missed.

Claude-Session: https://claude.ai/code/session_01RtK7xkMUveHaw6TdK1DF7L
…ests from it

Four gaps in the auth-provider selection, all found by review:

1. An unrecognised name (typo) kept every provider mounted and said nothing.
   The docstring claimed SM020 would report it, but diagnostics run in
   development only (app_builder gates run_diagnostics on is_development), so
   a deployed app silently mounted both providers, rendered two "Logout"
   entries, and let topological order decide which one won
   app.state.auth.auth_provider. select_auth_provider now always warns, and
   takes strict= (passed as `not is_development`) to raise InvalidModuleError
   in production — matching how discover_modules already treats strictness.

2. The shared `settings` fixture pinned every other env-sensitive knob but
   not auth_provider, so `SM_AUTH_PROVIDER=keycloak` in a .env — exactly what
   the new README tells Keycloak users to set — reshaped the suite. Pinning
   the fixture alone wasn't enough: module conftests build their own
   Settings. A session-scoped autouse fixture in the plugin pins the env var
   itself, which outranks .env and so covers every construction site. With
   keycloak in .env the suite went from 152 failures to 0.

3. test_app_state_has_sm_services read SM_AUTH_PROVIDER from the ambient
   environment while hard-asserting Keycloak was absent. It now passes
   auth_provider explicitly, and asserts the Services type before the
   module-list detail.

4. Blank `SM_AUTH_PROVIDER=` gave auth_provider == '', which matches no
   provider, while resolve_auth_provider() treated blank as the default — so
   the host and `make doctor` / gen-pages disagreed about the active
   provider. A field validator normalises blank to the default, mirroring
   _normalize_trusted_proxy in the same file.

Claude-Session: https://claude.ai/code/session_01RtK7xkMUveHaw6TdK1DF7L
@antosubash
antosubash marked this pull request as ready for review August 9, 2026 18:30
@antosubash
antosubash merged commit 4c2cb4a into main Aug 9, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants