Skip to content

fix(backend): register the missing liveness probe and make config injection real (#13162) - #13392

Merged
mrveiss merged 3 commits into
Dev_new_guifrom
issue-13162-sysint
Aug 3, 2026
Merged

mrveiss merged 3 commits into
Dev_new_guifrom
issue-13162-sysint

Conversation

@mrveiss

@mrveiss mrveiss commented Aug 3, 2026

Copy link
Copy Markdown
Owner

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.py asserts four basic
endpoints are routable. Three were; /api/hello returned 404. Grepping for the
path found it defined only in minimal_backend_test.py, a standalone dev
fixture -- never on the app that create_app() builds. Five consumers poll it
as the canonical "is the backend up" probe: the readiness gates in
api/security_endpoints_e2e_test.py and api/live_workflow_e2e_test.py (both
wait for a 200 before starting), plus diagnose_backend.py,
check_backend_status.py and demo_workflow_system.py. All five were waiting
on a response that could not arrive. The endpoint was missing, not the test.

Dependency injection (8 failures). dependency_injection_test.py asserts
Orchestrator, KnowledgeBase and Diagnostics accept an injected
config_manager. None of them did. The decisive evidence that the test was
right and the code wrong is dependencies.py: its FastAPI providers already
call all three with config_manager=, so four of them were broken -- two
raising TypeError on first invocation, two silently discarding the config they
were handed. #6983 had fixed one such provider by bending the caller to the
class's odd parameter name (config_mgr) rather than the class to its own
attribute 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_dependencies asserted a get_nested read and built a
temp reliability-stats file to feed it. The obvious move was to wire
DiagnosticsSettings.auto_apply_fixes (declared in models/settings.py, read
by nothing) into the permission prompt it documents. Checking first showed why
that would have been fake: ConfigManager.to_dict() exposes no diagnostics
section at all, so get_nested("diagnostics.auto_apply_fixes", False) can only
ever 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/hello alongside /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/version is 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_mgr renamed 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 ConfigManager global.
  • knowledge/base.py, knowledge/_composed.py --
    __init__(config_manager=None). The seven Redis/ChromaDB settings now read
    through the injected manager; the module-level global they used is removed.
    config_manager is deliberately not forwarded up the MRO -- the remaining
    mixins take no constructor arguments.
  • diagnostics.py -- __init__(config_manager=, llm_service=).
  • dependencies.py -- get_knowledge_base and get_cached_knowledge_base now
    pass the config they resolve instead of dropping it; get_orchestrator uses
    the corrected parameter name. get_diagnostics and get_cached_orchestrator
    needed no edit -- their existing calls became valid once the constructors did.

Production bug fixed on the way

  • orchestrator.py _ensure_working_llm_model read the fallback model from the
    module-level singleton, silently ignoring an injected ConfigManager. Now
    reads self.config_manager.

Test-side corrections (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 into the async
    _configure_llama_index step. Now asserts get().
  • Orchestrator asserted get_nested. OrchestratorConfig uses
    get_llm_config() plus get(). Now asserts get().
  • Diagnostics asserted get_nested. No diagnostics section is reachable
    through ConfigManager, so the test now asserts the contract
    dependencies.get_diagnostics actually needs -- the injected instances are the
    ones held. Reasoning is recorded in the test docstring.
  • 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.

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

Endpoint reachable unauthenticated through the real app:

PROBE /api/hello 200 {'message': 'AutoBot backend is running', 'status': 'ok', ...}
PROBE /api/version 200
PROBE /api/system/health 200

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.

autobot-backend/orchestration              5 failed / 303 passed   base and branch
autobot-backend/tests/middleware          53 passed               branch
autobot-backend/initialization            24 passed               branch

the five knowledge files that carry the suite's failures
(code_semantic_search, kb_optimization, search_quality,
 connectors/scheduler, connectors/connector_resilience)
                                          12 failed / 108 passed  base and 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/knowledge run reports 16 failures rather
than 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.py failures
reproduce exactly and are fixed. system_integration_test.py has exactly 16 test
functions, so CI's 16 is a total wipeout of that file -- consistent with
create_app() failing at setup_method under 3.14, a single root cause distinct
from the /api/hello routing bug fixed here. The 3.14-only failures could not be
reproduced: 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 compileall over api/, agents/, initialization/,
app_factory.py and main.py is clean, so the remaining 3.14 failures are
import- or runtime-level, not syntax.

Model Used

Claude Opus 5 (1M context)

Closes #13162

PARTIAL. This PR fixes only the three files it was scoped to
(system_integration_test.py, dependency_injection_test.py,
tests/test_startup_imports.py). #13162 covers the whole red Python suite and
stays open -- sibling PRs are repairing the other groups, and the
Python-3.14-only failures in system_integration_test.py and
tests/test_startup_imports.py are not addressed here.

…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).
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

✅ SSOT Configuration Compliance: Passing

🎉 No hardcoded values detected that have SSOT config equivalents!

github-actions Bot and others added 2 commits August 3, 2026 13:14
Auto-regenerated (py3.14) to match the backend schema so the required
verify-generated-types gate passes. Triggered by auto-fix-generated-types.yml.
@mrveiss
mrveiss merged commit 6175289 into Dev_new_gui Aug 3, 2026
46 of 61 checks passed
@mrveiss
mrveiss deleted the issue-13162-sysint branch August 3, 2026 15:00
@codecov

codecov Bot commented Aug 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

1 participant