fix: two host-install papercuts — import-time app construction and container-only identity paths - #72
Merged
Conversation
…ntainer-only identity paths Two bugs, both from container-only assumptions leaking into host installs. Bug 1 — ASGI app constructed at module import time. main.py had `asgi_app: BearerTokenMiddleware = create_asgi_app()` at module level. The console-script entry point imports main to reach main(), so merely running --help constructed the entire app and ran every startup guard. An operator with a broken config could not ask the binary anything at all. Fixed with PEP 562 lazy construction: _asgi_app cache + get_asgi_app() + module __getattr__, so main.asgi_app still works for gunicorn and tests but constructs on first access. _App.load() now calls get_asgi_app() explicitly (a bare global reference would NameError). The guards inside create_asgi_app() are byte-for-byte unchanged — only the timing moved. An unconfigured server still fails loud on an actual serve. Bug 2 — identity store defaulted to a container-only path. config.py had `api_keys_store_path = "/data/identity/api-keys.json"` and `entra_identities_store_path = "/data/identity/entra-identities.json"` hardcoded. On a host install /data is not writable, so every boot logged `identity_store.seed: could not write seed ... PermissionError(13)` and the keystore silently never persisted. Now defaults to a host-writable path via a new _default_identity_store_path() helper, matching the convention already documented in this file's own YamlConfigSettingsSource docstring. Graceful in-memory degradation is preserved. Verified this does not regress ACA/production: amplifier-online.yaml explicitly overrides entra_identities_store_path and never uses static mode. Verification: - uv run python -c "import context_intelligence_server.main" → IMPORT OK with no config - uv run context-intelligence-server --help → exits 0 with no config - Guard still fires on real serve (NEO4J_REQUIRE_EXPLICIT_CLIENTS=true → RuntimeError, worker exit 3) - uv run pytest tests/ -q -m "not neo4j" → 1821 passed, 4 skipped, 79 deselected - New tests/test_lazy_asgi_app.py (9 subprocess-based tests) - 2 pre-existing tests in test_identity_map_wire.py updated for new defaults Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…table identity paths) The prior commit on this branch landed only the new test file; the three source changes it exercises were left uncommitted. This adds them. - main.py: replace import-time `asgi_app = create_asgi_app()` with PEP 562 lazy construction (_asgi_app cache + get_asgi_app() + module __getattr__). The console-script entry point imports main to reach main(), so --help previously constructed the whole app and ran every startup guard. Guards inside create_asgi_app() are byte-for-byte unchanged - only the timing moved. _App.load() calls get_asgi_app() explicitly (a bare global would NameError once the unconditional assignment was removed). - config.py: api_keys_store_path / entra_identities_store_path defaulted to /data/identity/*.json, a container-only path. On a host install /data is not writable, so every boot logged PermissionError(13) and the keystore silently never persisted. New _default_identity_store_path() helper defaults to a host-writable location, matching the convention already documented in this file's own YamlConfigSettingsSource docstring. - tests/test_identity_map_wire.py: two pre-existing tests updated for the new defaults, plus an assertion that neither path is under /data/. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Diego Colombo (colombod)
approved these changes
Aug 15, 2026
Diego Colombo (colombod)
added a commit
that referenced
this pull request
Aug 17, 2026
…boot Rebase-integration fixes required where PR #70 (data-quality phase 2) and the work merged to main after it (PR #72/#73) touched the same lifespan region: - main.py: seed _sweep_task=None and queue_health="healthy" before the B1 deploy-safe boot boundary. The #73 periodic crash-recovery sweep task and the W-2 queue-health signal are both created INSIDE that boundary, so they must be seeded before it or the shutdown finally / a startup failure hits an unbound name. queue_health default is "healthy" per PR #70's W-2 contract (test_queue_recovery_success_leaves_queue_health_healthy). - test_queue_manager.py / test_main.py: update 5 QueueManager.commit() call sites in PR #73's tests to PR #70 I5b's 3-arg signature commit(sid, offset, cursor) (cursor has no default by design, spec 10.4). Cursor=None: these are queue-level tests, not cursor-durability tests. Preserves both feature sets: #73 bounded respawn + spool + sweep, and #70 deploy-safe boot + maintenance gate + W-2 queue health. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes two bugs introduced by container-only assumptions leaking into host installs.
Bug 1: ASGI app constructed at module import time
Issue:
main.pyhadasgi_app: BearerTokenMiddleware = create_asgi_app()at module level. The console-script entry point importsmainto reachmain(), so merely running--helpconstructed the entire app and ran every startup guard. An operator with a broken config could not ask the binary anything at all.Fix: PEP 562 lazy construction using
_asgi_appcache +get_asgi_app()+ module__getattr__. Nowmain.asgi_appstill works for gunicorn and tests but constructs on first access. The guards insidecreate_asgi_app()are byte-for-byte unchanged — only the timing moved. An unconfigured server still fails loud on an actual serve.Bug 2: Identity store defaulted to container-only path
Issue:
config.pyhadapi_keys_store_path = "/data/identity/api-keys.json"andentra_identities_store_path = "/data/identity/entra-identities.json"hardcoded. On a host install/datais not writable, so every boot loggedidentity_store.seed: could not write seed ... PermissionError(13)and the keystore silently never persisted.Fix: Defaults to a host-writable path via a new
_default_identity_store_path()helper, matching the convention already documented in this file's ownYamlConfigSettingsSourcedocstring. Graceful in-memory degradation is preserved. Verified this does not regress ACA/production:amplifier-online.yamlexplicitly overridesentra_identities_store_pathand never uses static mode.Files Changed
context_intelligence_server/main.py— lazy ASGI app constructioncontext_intelligence_server/config.py— host-writable identity store pathstests/test_identity_map_wire.py— updated for new defaultstests/test_lazy_asgi_app.py— NEW: 9 subprocess-based tests for lazy constructionTest Results
✅
uv run python -c "import context_intelligence_server.main"→ IMPORT OK with no config✅
uv run context-intelligence-server --help→ exits 0 with no config✅ Guard still fires on real serve (NEO4J_REQUIRE_EXPLICIT_CLIENTS=true → RuntimeError, worker exit 3)
✅
uv run pytest tests/ -q -m "not neo4j"→ 1821 passed, 4 skipped, 79 deselectedImportant Caveats
--versionflag note:--versionis not an implemented flag in this CLI and never was. The fix removes the import-time crash regardless of which flag is passed; an unrecognised flag now fails via argparse (exit 2) instead of via a startup guard.1b2a38d. Today an empty config boots with fail-closed warnings. The lazy-construction fix still stands on its own: import-time construction still runs other startup guards, which is what--helpshould never do.blob_path,queues_pathandlog_pathalso still default to/data/...at HEAD. This PR deliberately fixes only the identity paths, because theirs is the one failure that is silent — the others fail loudly at startup, while an unwritable identity store leaves the server running with a keystore that never persists.Generated with Amplifier