From 6bd0d640e601d3650090ab23379cd0a2aff94174 Mon Sep 17 00:00:00 2001 From: CREDO23 Date: Thu, 2 Jul 2026 19:54:20 +0200 Subject: [PATCH] refactor(capabilities): group framework into core/, keep verbs top-level --- .../builtins/scraping/tools/index.py | 2 +- .../app/capabilities/core/__init__.py | 30 +++++++++++++++++++ .../{ => core}/access/__init__.py | 0 .../capabilities/{ => core}/access/agent.py | 6 ++-- .../{ => core}/access/rate_limit.py | 0 .../capabilities/{ => core}/access/rest.py | 8 ++--- .../app/capabilities/{ => core}/billing.py | 2 +- .../app/capabilities/{ => core}/store.py | 2 +- .../app/capabilities/{ => core}/types.py | 0 .../capabilities/web/discover/definition.py | 3 +- .../app/capabilities/web/discover/executor.py | 2 +- .../app/capabilities/web/scrape/definition.py | 3 +- .../app/capabilities/web/scrape/executor.py | 2 +- surfsense_backend/app/routes/__init__.py | 2 +- .../capabilities/access/test_agent_tools.py | 4 +-- .../capabilities/access/test_rate_limit.py | 2 +- .../capabilities/access/test_rest_router.py | 14 ++++----- .../tests/unit/capabilities/test_billing.py | 6 ++-- .../tests/unit/capabilities/test_registry.py | 4 +-- 19 files changed, 60 insertions(+), 32 deletions(-) create mode 100644 surfsense_backend/app/capabilities/core/__init__.py rename surfsense_backend/app/capabilities/{ => core}/access/__init__.py (100%) rename surfsense_backend/app/capabilities/{ => core}/access/agent.py (89%) rename surfsense_backend/app/capabilities/{ => core}/access/rate_limit.py (100%) rename surfsense_backend/app/capabilities/{ => core}/access/rest.py (89%) rename surfsense_backend/app/capabilities/{ => core}/billing.py (99%) rename surfsense_backend/app/capabilities/{ => core}/store.py (90%) rename surfsense_backend/app/capabilities/{ => core}/types.py (100%) diff --git a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/scraping/tools/index.py b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/scraping/tools/index.py index 5c89dbe30..8416b8b05 100644 --- a/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/scraping/tools/index.py +++ b/surfsense_backend/app/agents/chat/multi_agent_chat/subagents/builtins/scraping/tools/index.py @@ -7,7 +7,7 @@ from typing import Any from langchain_core.tools import BaseTool from app.agents.chat.multi_agent_chat.shared.permissions import Ruleset -from app.capabilities.access.agent import build_capability_tools +from app.capabilities.core.access.agent import build_capability_tools from app.capabilities.web.discover.definition import WEB_DISCOVER from app.capabilities.web.scrape.definition import WEB_SCRAPE diff --git a/surfsense_backend/app/capabilities/core/__init__.py b/surfsense_backend/app/capabilities/core/__init__.py new file mode 100644 index 000000000..71d14b639 --- /dev/null +++ b/surfsense_backend/app/capabilities/core/__init__.py @@ -0,0 +1,30 @@ +"""Capability framework kernel: registry contracts, store, billing, and access doors.""" + +from app.capabilities.core.billing import charge_capability, gate_capability +from app.capabilities.core.store import ( + all_capabilities, + get_capability, + register_capability, +) +from app.capabilities.core.types import ( + BillableInput, + BillableOutput, + BillingUnit, + Capability, + CapabilityContext, + Executor, +) + +__all__ = [ + "BillableInput", + "BillableOutput", + "BillingUnit", + "Capability", + "CapabilityContext", + "Executor", + "all_capabilities", + "charge_capability", + "gate_capability", + "get_capability", + "register_capability", +] diff --git a/surfsense_backend/app/capabilities/access/__init__.py b/surfsense_backend/app/capabilities/core/access/__init__.py similarity index 100% rename from surfsense_backend/app/capabilities/access/__init__.py rename to surfsense_backend/app/capabilities/core/access/__init__.py diff --git a/surfsense_backend/app/capabilities/access/agent.py b/surfsense_backend/app/capabilities/core/access/agent.py similarity index 89% rename from surfsense_backend/app/capabilities/access/agent.py rename to surfsense_backend/app/capabilities/core/access/agent.py index 75b535840..003f42ae2 100644 --- a/surfsense_backend/app/capabilities/access/agent.py +++ b/surfsense_backend/app/capabilities/core/access/agent.py @@ -10,9 +10,9 @@ from __future__ import annotations from langchain_core.tools import BaseTool, StructuredTool -from app.capabilities.billing import charge_capability, gate_capability -from app.capabilities.store import all_capabilities -from app.capabilities.types import Capability, CapabilityContext +from app.capabilities.core.billing import charge_capability, gate_capability +from app.capabilities.core.store import all_capabilities +from app.capabilities.core.types import Capability, CapabilityContext from app.db import async_session_maker from app.services.web_crawl_credit_service import InsufficientCreditsError diff --git a/surfsense_backend/app/capabilities/access/rate_limit.py b/surfsense_backend/app/capabilities/core/access/rate_limit.py similarity index 100% rename from surfsense_backend/app/capabilities/access/rate_limit.py rename to surfsense_backend/app/capabilities/core/access/rate_limit.py diff --git a/surfsense_backend/app/capabilities/access/rest.py b/surfsense_backend/app/capabilities/core/access/rest.py similarity index 89% rename from surfsense_backend/app/capabilities/access/rest.py rename to surfsense_backend/app/capabilities/core/access/rest.py index 6a82e9144..df37c79ed 100644 --- a/surfsense_backend/app/capabilities/access/rest.py +++ b/surfsense_backend/app/capabilities/core/access/rest.py @@ -8,10 +8,10 @@ from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from app.auth.context import AuthContext -from app.capabilities.access.rate_limit import enforce_capability_rate_limit -from app.capabilities.billing import charge_capability, gate_capability -from app.capabilities.store import all_capabilities -from app.capabilities.types import Capability, CapabilityContext +from app.capabilities.core.access.rate_limit import enforce_capability_rate_limit +from app.capabilities.core.billing import charge_capability, gate_capability +from app.capabilities.core.store import all_capabilities +from app.capabilities.core.types import Capability, CapabilityContext from app.db import get_async_session from app.services.web_crawl_credit_service import InsufficientCreditsError from app.users import get_auth_context diff --git a/surfsense_backend/app/capabilities/billing.py b/surfsense_backend/app/capabilities/core/billing.py similarity index 99% rename from surfsense_backend/app/capabilities/billing.py rename to surfsense_backend/app/capabilities/core/billing.py index 636920a9f..a1fda82b2 100644 --- a/surfsense_backend/app/capabilities/billing.py +++ b/surfsense_backend/app/capabilities/core/billing.py @@ -6,7 +6,7 @@ from typing import TYPE_CHECKING from sqlalchemy import select -from app.capabilities.types import ( +from app.capabilities.core.types import ( BillableInput, BillableOutput, BillingUnit, diff --git a/surfsense_backend/app/capabilities/store.py b/surfsense_backend/app/capabilities/core/store.py similarity index 90% rename from surfsense_backend/app/capabilities/store.py rename to surfsense_backend/app/capabilities/core/store.py index b828480da..aad172c79 100644 --- a/surfsense_backend/app/capabilities/store.py +++ b/surfsense_backend/app/capabilities/core/store.py @@ -2,7 +2,7 @@ from __future__ import annotations -from app.capabilities.types import Capability +from app.capabilities.core.types import Capability _REGISTRY: dict[str, Capability] = {} diff --git a/surfsense_backend/app/capabilities/types.py b/surfsense_backend/app/capabilities/core/types.py similarity index 100% rename from surfsense_backend/app/capabilities/types.py rename to surfsense_backend/app/capabilities/core/types.py diff --git a/surfsense_backend/app/capabilities/web/discover/definition.py b/surfsense_backend/app/capabilities/web/discover/definition.py index e4e779307..f672c6bbb 100644 --- a/surfsense_backend/app/capabilities/web/discover/definition.py +++ b/surfsense_backend/app/capabilities/web/discover/definition.py @@ -2,8 +2,7 @@ from __future__ import annotations -from app.capabilities.store import register_capability -from app.capabilities.types import Capability +from app.capabilities.core import Capability, register_capability from app.capabilities.web.discover.executor import build_discover_executor from app.capabilities.web.discover.schemas import DiscoverInput, DiscoverOutput diff --git a/surfsense_backend/app/capabilities/web/discover/executor.py b/surfsense_backend/app/capabilities/web/discover/executor.py index e98de86e4..3660aedcd 100644 --- a/surfsense_backend/app/capabilities/web/discover/executor.py +++ b/surfsense_backend/app/capabilities/web/discover/executor.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections.abc import Sequence -from app.capabilities.types import Executor +from app.capabilities.core import Executor from app.capabilities.web.discover.providers.base import DiscoverProvider from app.capabilities.web.discover.schemas import DiscoverInput, DiscoverOutput diff --git a/surfsense_backend/app/capabilities/web/scrape/definition.py b/surfsense_backend/app/capabilities/web/scrape/definition.py index 5ceb0b3a5..f6ccafe02 100644 --- a/surfsense_backend/app/capabilities/web/scrape/definition.py +++ b/surfsense_backend/app/capabilities/web/scrape/definition.py @@ -2,8 +2,7 @@ from __future__ import annotations -from app.capabilities.store import register_capability -from app.capabilities.types import BillingUnit, Capability +from app.capabilities.core import BillingUnit, Capability, register_capability from app.capabilities.web.scrape.executor import build_scrape_executor from app.capabilities.web.scrape.schemas import ScrapeInput, ScrapeOutput diff --git a/surfsense_backend/app/capabilities/web/scrape/executor.py b/surfsense_backend/app/capabilities/web/scrape/executor.py index 05459ee6a..8651ff738 100644 --- a/surfsense_backend/app/capabilities/web/scrape/executor.py +++ b/surfsense_backend/app/capabilities/web/scrape/executor.py @@ -2,7 +2,7 @@ from __future__ import annotations -from app.capabilities.types import Executor +from app.capabilities.core import Executor from app.capabilities.web.scrape.schemas import ScrapeInput, ScrapeOutput, ScrapeRow from app.proprietary.web_crawler import ( CrawlOutcome, diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py index df53759aa..9f42f11e1 100644 --- a/surfsense_backend/app/routes/__init__.py +++ b/surfsense_backend/app/routes/__init__.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends import app.capabilities.web # noqa: F401 (registers web.* verbs before the door builds) from app.automations.api import router as automations_router -from app.capabilities.access.rest import build_capabilities_router +from app.capabilities.core.access.rest import build_capabilities_router from app.file_storage.api import router as file_storage_router from app.gateway import require_gateway_enabled from app.notifications.api import router as notifications_router diff --git a/surfsense_backend/tests/unit/capabilities/access/test_agent_tools.py b/surfsense_backend/tests/unit/capabilities/access/test_agent_tools.py index dc8e126a6..ac3db487d 100644 --- a/surfsense_backend/tests/unit/capabilities/access/test_agent_tools.py +++ b/surfsense_backend/tests/unit/capabilities/access/test_agent_tools.py @@ -6,7 +6,7 @@ from unittest.mock import AsyncMock import pytest from pydantic import BaseModel, Field -from app.capabilities.types import BillingUnit, Capability +from app.capabilities.core.types import BillingUnit, Capability from app.services.web_crawl_credit_service import InsufficientCreditsError pytestmark = pytest.mark.asyncio @@ -54,7 +54,7 @@ class _FakeSessionCtx: @pytest.fixture def isolate(monkeypatch): """Stub the billing session + charge/gate so tools never hit the DB.""" - from app.capabilities.access import agent as mod + from app.capabilities.core.access import agent as mod monkeypatch.setattr(mod, "async_session_maker", lambda: _FakeSessionCtx()) charge = AsyncMock() diff --git a/surfsense_backend/tests/unit/capabilities/access/test_rate_limit.py b/surfsense_backend/tests/unit/capabilities/access/test_rate_limit.py index f9c8076bf..1762c146e 100644 --- a/surfsense_backend/tests/unit/capabilities/access/test_rate_limit.py +++ b/surfsense_backend/tests/unit/capabilities/access/test_rate_limit.py @@ -4,7 +4,7 @@ import pytest from fastapi import HTTPException from starlette.requests import Request -from app.capabilities.access import rate_limit +from app.capabilities.core.access import rate_limit def _request(workspace_id: int) -> Request: diff --git a/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py b/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py index 211ce9854..b874b8bba 100644 --- a/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py +++ b/surfsense_backend/tests/unit/capabilities/access/test_rest_router.py @@ -7,7 +7,7 @@ from fastapi import FastAPI from httpx import ASGITransport, AsyncClient from pydantic import BaseModel -from app.capabilities.types import Capability +from app.capabilities.core.types import Capability from app.db import get_async_session from app.users import get_auth_context @@ -36,8 +36,8 @@ _ECHO = Capability( def _build_app(capabilities, monkeypatch) -> FastAPI: """Mount the generated door with auth/workspace/session/rate-limit stubbed.""" - from app.capabilities.access import rest - from app.capabilities.access.rate_limit import enforce_capability_rate_limit + from app.capabilities.core.access import rest + from app.capabilities.core.access.rate_limit import enforce_capability_rate_limit monkeypatch.setattr(rest, "check_workspace_access", _noop_async, raising=True) @@ -91,7 +91,7 @@ async def test_input_is_validated_against_the_verb_schema(monkeypatch): def test_registered_verbs_appear_on_rest(): """A verb in the registry shows up as a route with no per-verb wiring.""" import app.capabilities.web # noqa: F401 (registers web.* at import) - from app.capabilities.access import rest + from app.capabilities.core.access import rest router = rest.build_capabilities_router() paths = {route.path for route in router.routes} @@ -101,7 +101,7 @@ def test_registered_verbs_appear_on_rest(): @pytest.mark.asyncio async def test_over_budget_is_blocked_before_the_executor(monkeypatch): - from app.capabilities.access import rest + from app.capabilities.core.access import rest from app.services.web_crawl_credit_service import InsufficientCreditsError async def _raise(*args, **kwargs): @@ -123,7 +123,7 @@ async def test_over_budget_is_blocked_before_the_executor(monkeypatch): @pytest.mark.asyncio async def test_rate_limit_blocks_the_workspace(monkeypatch): """The generated route enforces the per-workspace limit (429).""" - from app.capabilities.access import rate_limit, rest + from app.capabilities.core.access import rate_limit, rest monkeypatch.setattr(rest, "check_workspace_access", _noop_async, raising=True) monkeypatch.setattr( @@ -153,7 +153,7 @@ async def test_rate_limit_blocks_the_workspace(monkeypatch): async def test_success_charges_once(monkeypatch): from unittest.mock import AsyncMock - from app.capabilities.access import rest + from app.capabilities.core.access import rest charge = AsyncMock() monkeypatch.setattr(rest, "charge_capability", charge, raising=True) diff --git a/surfsense_backend/tests/unit/capabilities/test_billing.py b/surfsense_backend/tests/unit/capabilities/test_billing.py index 1f70afc56..c0a74878b 100644 --- a/surfsense_backend/tests/unit/capabilities/test_billing.py +++ b/surfsense_backend/tests/unit/capabilities/test_billing.py @@ -11,9 +11,9 @@ from uuid import UUID import pytest -import app.capabilities.billing as billing -from app.capabilities.billing import charge_capability, gate_capability -from app.capabilities.types import BillingUnit, CapabilityContext +import app.capabilities.core.billing as billing +from app.capabilities.core.billing import charge_capability, gate_capability +from app.capabilities.core.types import BillingUnit, CapabilityContext from app.capabilities.web.scrape.schemas import ScrapeInput, ScrapeOutput, ScrapeRow from app.config import config from app.services.web_crawl_credit_service import InsufficientCreditsError diff --git a/surfsense_backend/tests/unit/capabilities/test_registry.py b/surfsense_backend/tests/unit/capabilities/test_registry.py index 3b941224c..c603c02eb 100644 --- a/surfsense_backend/tests/unit/capabilities/test_registry.py +++ b/surfsense_backend/tests/unit/capabilities/test_registry.py @@ -7,8 +7,8 @@ import pytest from app.capabilities import ( web, # noqa: F401 — importing the namespace registers its verbs ) -from app.capabilities.store import get_capability -from app.capabilities.types import BillingUnit +from app.capabilities.core.store import get_capability +from app.capabilities.core.types import BillingUnit from app.capabilities.web.discover.schemas import DiscoverInput, DiscoverOutput from app.capabilities.web.scrape.schemas import ScrapeInput, ScrapeOutput