Repository navigation
fix(backend): register the missing liveness probe and make config injection real (#13162) - #13392
Merged
Merged
Conversation
…ection real (#13162) Repairs the 9 red results in system_integration_test.py, dependency_injection_test.py and tests/test_startup_imports.py. Seven of the nine turned out to be production defects, not stale tests. Missing liveness endpoint ------------------------- /api/hello was never registered on the app built by create_app() -- it existed only on the standalone minimal_backend_test fixture. Five consumers poll it as the canonical "is the backend up" probe: api/security_endpoints_e2e_test.py readiness gate, waits for 200 api/live_workflow_e2e_test.py readiness gate shared/scripts/diagnose_backend.py "Basic API" check shared/scripts/analysis/check_backend_status.py shared/scripts/utilities/demo_workflow_system.py Every one of them waited for a 200 that could not arrive. Registered the route next to /api/version in initialization/endpoints.py and added it to both service-auth exemption lists, so it answers unauthenticated the way the probes expect. The handler touches no Redis, database or config so it can answer while the rest of startup is still in progress. Dependency injection was half-wired ----------------------------------- dependencies.py has provider functions that pass config_manager= into Orchestrator, KnowledgeBase and Diagnostics. None of the three accepted it, so four of those providers were broken: get_diagnostics -> Diagnostics(config_manager=) TypeError get_cached_orchestrator -> Orchestrator(config_manager=) TypeError get_knowledge_base -> config accepted, then dropped get_cached_knowledge_base-> config accepted, then dropped #6983 fixed get_orchestrator by bending the caller to the odd parameter name (config_mgr) instead of the class to its own attribute name, and missed the other four. This change moves the classes to the contract their callers and the DI test already assume: Orchestrator.__init__(config_manager=, llm_service=, knowledge_base=, diagnostics=) -- config_mgr renamed to match the attribute it sets; llm_service, knowledge_base and diagnostics are now injectable and default to the canonical singletons. KnowledgeBaseCore/KnowledgeBase.__init__(config_manager=) -- the seven Redis/ChromaDB settings now read through the injected manager instead of a module-level global, which is removed. Diagnostics.__init__(config_manager=, llm_service=) One further production bug found on the way: orchestrator.py _ensure_working_llm_model read the fallback model from the module-level ConfigManager singleton, silently ignoring an injected one. Now reads self.config_manager. Test-side corrections --------------------- Four assertions named APIs the code does not use. Corrected, not weakened -- each still proves the injected instance is the one consulted: llm_interface -> llm_service on Orchestrator and Diagnostics. #3185 retired LLMInterface in favour of LLMService; resurrecting the old name would re-introduce an alias the repo deliberately removed. KnowledgeBase asserted get_llm_config/get_nested. It reads its settings through get(); embedding-model resolution moved to the async _configure_llama_index step. Asserts get() instead. Orchestrator asserted get_nested. OrchestratorConfig uses get_llm_config() plus get(). Asserts get() instead. Diagnostics asserted get_nested and built a temp reliability-stats file to feed it. ConfigManager exposes no diagnostics section at all, so every diagnostics.* lookup can only return the caller's default -- asserting a config read there would assert fake wiring. The test now asserts the contract dependencies.get_diagnostics actually needs: the injected instances are the ones held. Recorded on #12750. The mock ConfigManager returned bare Mocks from get(), which no numeric consumer can use -- asyncio.Semaphore(Mock) raised TypeError. The real manager returns the caller's default for keys the settings model does not declare, so the mock now mirrors that. Malformed nosec annotations --------------------------- Three suppressions in the touched files spelled the reason as trailing prose after the test IDs: # nosec B404 - controlled system diagnostics bandit parses everything after the IDs as more IDs, so it emitted a "Test in comment: controlled is not a test name or id" warning for each word. The suppression itself worked, but the noise fails scripts/pr-preflight.sh, which requires bandit to be silent. Moved the prose behind a second hash, which is the documented form: # nosec B404 # controlled system diagnostics Still exactly 3 suppressed findings, now with no parse warnings. Verification ------------ SLM_SECRET_KEY=throwaway SLM_ENCRYPTION_KEY=x python3 -m pytest \ autobot-backend/system_integration_test.py \ autobot-backend/dependency_injection_test.py \ autobot-backend/tests/test_startup_imports.py \ -q -p no:cacheprovider \ -m "not integration and not slow and not distributed and not performance" before: 9 failed, 368 passed after: 377 passed /api/hello now answers 200 unauthenticated through create_app(). autobot-backend/orchestration: 5 failed / 303 passed both before and after (pre-existing, unrelated to this change).
Contributor
✅ SSOT Configuration Compliance: Passing🎉 No hardcoded values detected that have SSOT config equivalents! |
Auto-regenerated (py3.14) to match the backend schema so the required verify-generated-types gate passes. Triggered by auto-fix-generated-types.yml.
3 tasks
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
Thinking Path
Three startup/wiring test files were red. Because these tests exercise boot and
dependency wiring rather than business logic, the working hypothesis was that a
failure means a genuinely broken import or a genuinely unwired dependency. That
held for 7 of the 9 local failures.
/api/hello(1 failure).system_integration_test.pyasserts four basicendpoints are routable. Three were;
/api/helloreturned 404. Grepping for thepath found it defined only in
minimal_backend_test.py, a standalone devfixture -- never on the app that
create_app()builds. Five consumers poll itas the canonical "is the backend up" probe: the readiness gates in
api/security_endpoints_e2e_test.pyandapi/live_workflow_e2e_test.py(bothwait for a 200 before starting), plus
diagnose_backend.py,check_backend_status.pyanddemo_workflow_system.py. All five were waitingon a response that could not arrive. The endpoint was missing, not the test.
Dependency injection (8 failures).
dependency_injection_test.pyassertsOrchestrator,KnowledgeBaseandDiagnosticsaccept an injectedconfig_manager. None of them did. The decisive evidence that the test wasright and the code wrong is
dependencies.py: its FastAPI providers alreadycall all three with
config_manager=, so four of them were broken -- tworaising
TypeErroron first invocation, two silently discarding the config theywere handed.
#6983had fixed one such provider by bending the caller to theclass's odd parameter name (
config_mgr) rather than the class to its ownattribute name (
self.config_manager), and missed the rest.Tracing the injection through turned up one more live defect: the orchestrator
read its LLM fallback model from the module-level config singleton, ignoring any
injected manager.
Four test assertions did name APIs the code does not call. Those were corrected
to the API each class actually uses -- the injection claim they exist to prove is
preserved in every case.
One assertion deserves its own note, because the tempting fix was the wrong one.
test_diagnostics_with_dependenciesasserted aget_nestedread and built atemp reliability-stats file to feed it. The obvious move was to wire
DiagnosticsSettings.auto_apply_fixes(declared inmodels/settings.py, readby nothing) into the permission prompt it documents. Checking first showed why
that would have been fake:
ConfigManager.to_dict()exposes nodiagnosticssection at all, so
get_nested("diagnostics.auto_apply_fixes", False)can onlyever return the default. Wiring it would have shipped a knob that is
unreachable by construction. The wiring was reverted and the finding recorded on
#12750 instead.
What Changed
Missing liveness endpoint
initialization/endpoints.py-- registered/api/helloalongside/api/version.The handler touches no Redis, database or config, so it answers while the rest
of startup is still in progress, which is what a readiness gate needs.
middleware/service_auth_enforcement.py,middleware/service_auth_logging.py-- added the path to both service-auth exemption lists, matching how
/api/versionis treated, so the probes get a 200 rather than a 401.Dependency injection made real
orchestrator.py--__init__(config_manager=, llm_service=, knowledge_base=, diagnostics=).config_mgrrenamed to match the attribute it has always set;the other three collaborators are now injectable and default to the canonical
singletons. Also removed the module-level
ConfigManagerglobal.knowledge/base.py,knowledge/_composed.py--__init__(config_manager=None). The seven Redis/ChromaDB settings now readthrough the injected manager; the module-level global they used is removed.
config_manageris deliberately not forwarded up the MRO -- the remainingmixins take no constructor arguments.
diagnostics.py--__init__(config_manager=, llm_service=).dependencies.py--get_knowledge_baseandget_cached_knowledge_basenowpass the config they resolve instead of dropping it;
get_orchestratorusesthe corrected parameter name.
get_diagnosticsandget_cached_orchestratorneeded no edit -- their existing calls became valid once the constructors did.
Production bug fixed on the way
orchestrator.py_ensure_working_llm_modelread the fallback model from themodule-level singleton, silently ignoring an injected
ConfigManager. Nowreads
self.config_manager.Test-side corrections (corrected, not weakened -- each still proves the
injected instance is the one consulted)
llm_interface->llm_serviceonOrchestratorandDiagnostics.#3185retired
LLMInterfacein favour ofLLMService; resurrecting the old namewould re-introduce an alias the repo deliberately removed.
KnowledgeBaseassertedget_llm_config/get_nested. It reads its settingsthrough
get(); embedding-model resolution moved into the async_configure_llama_indexstep. Now assertsget().Orchestratorassertedget_nested.OrchestratorConfigusesget_llm_config()plusget(). Now assertsget().Diagnosticsassertedget_nested. Nodiagnosticssection is reachablethrough
ConfigManager, so the test now asserts the contractdependencies.get_diagnosticsactually needs -- the injected instances are theones held. Reasoning is recorded in the test docstring.
ConfigManagerreturned bareMocks fromget(), which no numericconsumer can use --
asyncio.Semaphore(Mock)raisedTypeError. The realmanager returns the caller's default for keys the settings model does not
declare, so the mock now mirrors that.
Verification
Endpoint reachable unauthenticated through the real app:
No regression in the blast radius. Every number below is a pair: the same
command run in a worktree checked out at the base commit, then on this branch.
The knowledge failures are pre-existing and unrelated -- Redis-dependent leader
election and connector checkpoints, plus search-quality date filters. None of
them touch
KnowledgeBaseCore.__init__.A whole-directory
autobot-backend/knowledgerun reports 16 failures ratherthan 12 (four more appear only under full-suite ordering); it is reported here
at file granularity because the whole-directory run at the base commit had to be
abandoned after exceeding its timeout twice under load.
Lint clean on all nine changed files:
isort,black,flake8(0 findings),bandit -c .bandit(no issues identified, no severity floor).Local Python is 3.10 and CI runs 3.14. CI reported 16/8/4 for these three files;
locally the baseline was 1/8/0. The 8
dependency_injection_test.pyfailuresreproduce exactly and are fixed.
system_integration_test.pyhas exactly 16 testfunctions, so CI's 16 is a total wipeout of that file -- consistent with
create_app()failing atsetup_methodunder 3.14, a single root cause distinctfrom the
/api/hellorouting bug fixed here. The 3.14-only failures could not bereproduced: that interpreter is present on the dev box but has no project
dependencies installed, and installing them ad hoc is not permitted.
python3.14 -m compilealloverapi/,agents/,initialization/,app_factory.pyandmain.pyis clean, so the remaining 3.14 failures areimport- or runtime-level, not syntax.
Model Used
Claude Opus 5 (1M context)
Closes #13162