Skip to content

system_interface: relocate test-only DI seams off prod-facing signatures#185

Draft
weishi-imbue wants to merge 2 commits into
mainfrom
wz/di-seams-refactor
Draft

system_interface: relocate test-only DI seams off prod-facing signatures#185
weishi-imbue wants to merge 2 commits into
mainfrom
wz/di-seams-refactor

Conversation

@weishi-imbue

@weishi-imbue weishi-imbue commented Jun 20, 2026

Copy link
Copy Markdown
Contributor

Stacked on #175 (wz/message-location-from-observe) — base this PR against that branch, not main. The branch has been merged up to the latest #175 (which itself pulled in main, including the new latchkey HTTP-client feature).

What & why

Every injection point reviewed in #175 (_send_message_to_agent(discover=, send=), create_application(agent_manager=, claude_auth_service=, welcome_resender=), and the preconfigured_* app-state keys) is test-onlymain.py passes none of them. They exist to satisfy this package's anti-mock ratchets (no monkeypatch.setattr, capped unittest.mock), so collaborators must be injected explicitly rather than patched. The problem is placement: a seam on a public/private call signature or a factory parameter leaks test concerns into the prod surface.

This PR keeps the dependencies injectable (ratchet-mandated) but moves the injection points onto FastAPI's own DI, so the prod signatures read as if there were no tests. No prod behavior changes; no ratchet-count changes.

Changes

  1. MngrMessenger — the discover/send collaborators move off the _send_message_to_agent defaulted-param signature onto a FrozenModel that AgentManager owns. Prod calls messenger.send_to_agent(...); the four branch-table tests construct MngrMessenger(discover=, send=) directly. The send_message free function and _send_message_to_agent are deleted.
  2. auth + welcome → DependsClaudeAuthService and WelcomeResender move from create_application params + app.state reads to Depends(providers.get_claude_auth_service) / Depends(get_welcome_resender). Tests substitute fakes via app.dependency_overrides. ClaudeAuthService stays long-lived per app (the OAuth subprocess must survive between /start and /submit-code); only its wiring changed.
  3. agent_manager → Depends (seam fully removed) — every handler, the two non-route helpers (_find_agent, _get_or_create_watcher, threaded the manager from their callers), and the service_dispatcher routes now resolve the live AgentManager through Depends(get_agent_manager), and its paired WebSocketBroadcaster through Depends(get_broadcaster) (derived from the manager as a sub-dependency, so one override covers both consistently). The lifespan unconditionally builds/starts/owns the real manager; suppressing mngr observe in tests is handled independently by conftest's AgentManager.start no-op. Tests set app.dependency_overrides[get_agent_manager], so create_application carries no agent-manager test param, no preconfigured_* key, and the lifespan has no test branch.

get_agent_manager is typed on HTTPConnection (the base of Request and WebSocket) so one provider resolves for both HTTP and WebSocket routes. The service-dispatcher websocket is registered via a wrapper, so the wrapper (the actual route) resolves the manager via Depends and passes it to the helper.

One deviation from the original spec

The spec's get_claude_auth_service() returned a fresh ClaudeAuthService() per request, which breaks production (the OAuth subprocess must survive across the separate /start and /submit-code requests; Depends caches only within one request). The provider reads the long-lived instance off app.state instead — still deletes the factory param while preserving the invariant.

Tests

cd apps/system_interface && uv run pytest485 passed, 10 skipped, coverage 87.55%. The 3 remaining non-passes are pre-existing environmental sandbox issues, not regressions (all pass in CI):

  • test_start_observe_spawns_long_lived_subprocess — fails identically on the base branch; this sandbox's mngr profile settings.toml lacks is_allowed_in_pytest = true.
  • test_e2e.py (2) — Chromium not installed (deferred-install); the fixtures run past their dependency_overrides setup and only the browser launch fails.

Ratchet counts unchanged (dependency_overrides is neither setattr nor unittest.mock).

🤖 Generated with Claude Code

Reimplements the DI/test-seam refactor against the FastAPI->Flask
migration that landed on main (PR #175 merged it in). The prior approach
used FastAPI Depends/dependency_overrides, neither of which exists under
flask-sock; this version achieves the same goal with the Flask state
container.

- create_application drops the agent_manager / claude_auth_service /
  welcome_resender test-only params. It unconditionally builds and starts
  the live AgentManager (conftest's AgentManager.start no-op keeps that
  cheap and observe-free under test). Tests that need a seeded manager
  build the app, then set state_of(app).agent_manager; auth tests set
  state_of(app).claude_auth_service / .welcome_resender.

- SystemInterfaceState.broadcaster is now derived from agent_manager (a
  property) instead of a stored field, so a single manager seed repoints
  the broadcaster too and the two can never diverge. The
  is_agent_manager_owned flag is gone; the app always owns the manager it
  builds, so shutdown() always stops it.

- Agent messaging: the discover/send collaborators move off the private
  _send_message_to_agent's defaulted params onto a MngrMessenger
  FrozenModel that AgentManager owns. The send_message free function and
  _send_message_to_agent are gone; tests construct MngrMessenger(discover=,
  send=) directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@weishi-imbue weishi-imbue force-pushed the wz/di-seams-refactor branch from 9aa6145 to eb9d5d2 Compare June 22, 2026 18:55
@weishi-imbue weishi-imbue changed the base branch from wz/message-location-from-observe to main June 26, 2026 00:38
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