Skip to content

ENG-670: Generalized per-provider capability resolution#2

Merged
clawhavenapp merged 1 commit into
mainfrom
commonzenpython/eng-670-generalized-per-provider-capability-resolution
Jul 7, 2026
Merged

ENG-670: Generalized per-provider capability resolution#2
clawhavenapp merged 1 commit into
mainfrom
commonzenpython/eng-670-generalized-per-provider-capability-resolution

Conversation

@czpython

@czpython czpython commented Jul 6, 2026

Copy link
Copy Markdown
Owner

What

Every capability ticket (pause/resume ENG-526, snapshot ENG-528/527, discovery ENG-535) assumes a generalized per-provider capability resolver that didn't exist: providers/capabilities.py only had get_default_http_proxy_capability(), which checked the default provider only. With four providers (aws, exe, docker, hetzner) on the same deployment, a capability can be implemented by some providers and not others.

  • resolve_capability(provider, capability) (src/providers/capabilities.py): narrows a specific VMProvider instance to a capability ABC. isinstance-based, keeping the deliberate ABC-not-Protocol choice.
  • CapabilityUnsupportedError (src/providers/exceptions.py): the one shared, neutral error every capability-gated path raises on an unsupported provider — instead of an AttributeError.
  • http-proxy migration (src/http_proxies/service.py): resolution moved from eager default-provider lookup in the constructor to per-operation resolution — the default provider for account-bound create/delete (no host in play), the host's own provider for attach/detach. The API layer maps unsupported to the existing 501 HTTP_PROXY_UNSUPPORTED; the now-dead constructor try/except in deps.py is removed.
  • exe stays the only implementer; on today's exe-default deployments the endpoints behave exactly as before.

Why

Shared prerequisite for the drukbox capability roadmap (pause/resume, snapshot/fork, discovery), found unticketed in the 2026-07-06 ticket-landscape audit. Without it, every capability feature would re-invent default-provider-only gating that breaks the moment a host lives on a non-default provider.

Acceptance criteria

  • A capability-gated endpoint resolves against the host's provider, not the default provider
  • http-proxy endpoints behave exactly as today after the migration
  • A provider without the capability produces the shared unsupported error, not an AttributeError
  • Tests cover: capability present, capability absent, non-default provider with capability

Verification

uv run ruff check, uv run ruff format --check, uv run pyright, uv run pytest (277 passed) — all green.

🤖 Generated with Claude Code

resolve_capability() narrows any VMProvider instance to a capability
ABC (isinstance-based, keeping the deliberate ABC-not-Protocol choice)
and raises the shared CapabilityUnsupportedError when the provider
doesn't implement it. HTTPProxyService now resolves per operation:
the default provider for account-bound create/delete, the host's own
provider for attach/detach — so a host on a non-default provider gates
on its own provider's capability instead of the deployment default.
Unsupported still maps to 501 HTTP_PROXY_UNSUPPORTED; exe remains the
only implementer and http-proxy endpoints behave as before.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@clawhavenapp
clawhavenapp merged commit 0d7fbb5 into main Jul 7, 2026
6 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