Files
hamza.khyari b028c80cab
CI / benchmark-publish (pull_request) Has been skipped
CI / lint (pull_request) Successful in 14s
CI / build (pull_request) Successful in 15s
CI / quality (pull_request) Successful in 18s
CI / security (pull_request) Successful in 33s
CI / typecheck (pull_request) Successful in 41s
CI / unit_tests (pull_request) Successful in 2m29s
CI / integration_tests (pull_request) Successful in 3m2s
CI / docker (pull_request) Successful in 39s
CI / coverage (pull_request) Successful in 4m28s
CI / benchmark-regression (pull_request) Successful in 29m5s
feat(acms): add scoped backend view filtering
Add project-resource isolation to the ACMS context tier system via
ScopedBackendView, ResourceScope, alias resolution, DAG expansion,
and enforcement hooks.

Core types (scoped_view.py): ResourceScope, ScopeViolationError,
ScopedBackendView, ScopedBackendSet, create_scoped_backend_set.

Scope resolution (scope_resolution.py): ResourceAliasResolver,
validate_project_scope, validate_resource_scope, resolve_resource_scope
with registry_lookup callback for DAG expansion.

Tier integration (scoped_tiers.py): ScopedTierMixin with get_scoped,
get_scoped_by_resource, validate_fragment_scope, store_with_scope_check,
get_scoped_metrics mixed into ContextTierService.

Backward compat: tiers.py re-exports ScopedBackendView.

Tests: 74 Behave scenarios (235 steps), 8 Robot Framework cases,
7 ASV benchmark suites. Lint and typecheck clean.

ISSUES CLOSED: #193
2026-03-06 00:49:33 +00:00

271 lines
8.6 KiB
Python

"""ASV benchmarks for ACMS Scoped Backend View filtering.
Measures the performance of:
- ResourceScope creation and containment checks
- ResourceScope path matching with include/exclude globs
- ScopedBackendView fragment visibility filtering
- ScopedBackendView backend search proxying overhead
- ScopedBackendSet creation and multi-backend search
- ResourceAliasResolver alias resolution
- resolve_resource_scope allowlist/denylist resolution
- validate_project_scope and validate_resource_scope guards
"""
from __future__ import annotations
import importlib
import sys
from pathlib import Path
# Ensure the local *source* tree is importable even when ASV has an
# older build of the package installed.
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
sys.path.insert(0, _SRC)
# Force-reload so ASV picks up the source tree version.
import cleveragents # noqa: E402
importlib.reload(cleveragents)
from cleveragents.domain.models.acms.scope_resolution import ( # noqa: E402
resolve_resource_scope,
validate_project_scope,
validate_resource_scope,
)
from cleveragents.domain.models.acms.scoped_view import ( # noqa: E402
ResourceScope,
ScopedBackendView,
create_scoped_backend_set,
)
from cleveragents.domain.models.acms.stubs import ( # noqa: E402
InMemoryGraphBackend,
InMemoryTextBackend,
InMemoryVectorBackend,
)
from cleveragents.domain.models.acms.tiers import ( # noqa: E402
ContextTier,
TieredFragment,
)
from cleveragents.domain.models.core.project import ( # noqa: E402
LinkedResource,
NamespacedProject,
)
# ---------------------------------------------------------------------------
# ResourceScope benchmarks
# ---------------------------------------------------------------------------
class ResourceScopeCreationSuite:
"""Benchmark ResourceScope instantiation overhead."""
timeout = 60
def time_create_small_scope(self) -> None:
ResourceScope(
resource_ids=frozenset({"R1", "R2"}),
project_names=frozenset({"local/api"}),
)
def time_create_large_scope(self) -> None:
ResourceScope(
resource_ids=frozenset(f"R{i:04d}" for i in range(1000)),
project_names=frozenset(f"ns/proj{i}" for i in range(10)),
)
def time_contains_resource(self) -> None:
scope = ResourceScope(
resource_ids=frozenset(f"R{i:04d}" for i in range(1000)),
project_names=frozenset({"p1"}),
)
scope.contains_resource("R0500")
def time_contains_project(self) -> None:
scope = ResourceScope(
resource_ids=frozenset({"R1"}),
project_names=frozenset(f"ns/proj{i}" for i in range(100)),
)
scope.contains_project("ns/proj50")
# ---------------------------------------------------------------------------
# Path matching benchmarks
# ---------------------------------------------------------------------------
class PathMatchingSuite:
"""Benchmark ResourceScope.matches_path() with various patterns."""
timeout = 60
def setup(self) -> None:
self.scope_include = ResourceScope(
resource_ids=frozenset({"R1"}),
project_names=frozenset({"p1"}),
include_paths=("src/**/*.py", "tests/**/*.py"),
)
self.scope_exclude = ResourceScope(
resource_ids=frozenset({"R1"}),
project_names=frozenset({"p1"}),
exclude_paths=("**/node_modules/**", "**/__pycache__/**"),
)
self.scope_both = ResourceScope(
resource_ids=frozenset({"R1"}),
project_names=frozenset({"p1"}),
include_paths=("src/**/*.py",),
exclude_paths=("**/test_*",),
)
def time_match_include_hit(self) -> None:
self.scope_include.matches_path("src/models/user.py")
def time_match_include_miss(self) -> None:
self.scope_include.matches_path("docs/readme.md")
def time_match_exclude_pass(self) -> None:
self.scope_exclude.matches_path("src/main.py")
def time_match_exclude_reject(self) -> None:
self.scope_exclude.matches_path("node_modules/foo/bar.js")
def time_match_combined(self) -> None:
self.scope_both.matches_path("src/main.py")
# ---------------------------------------------------------------------------
# ScopedBackendView benchmarks
# ---------------------------------------------------------------------------
class FragmentFilteringSuite:
"""Benchmark ScopedBackendView.is_visible() fragment filtering."""
timeout = 60
def setup(self) -> None:
self.view = ScopedBackendView(
allowed_projects=frozenset({"proj-a"}),
resource_ids=frozenset({"R1", "R2"}),
)
self.frag_visible = TieredFragment(
fragment_id="f1",
content="c",
tier=ContextTier("hot"),
project_name="proj-a",
resource_id="R1",
)
self.frag_hidden = TieredFragment(
fragment_id="f2",
content="c",
tier=ContextTier("hot"),
project_name="proj-b",
resource_id="R1",
)
def time_visible_fragment(self) -> None:
self.view.is_visible(self.frag_visible)
def time_hidden_fragment(self) -> None:
self.view.is_visible(self.frag_hidden)
def time_effective_scope(self) -> None:
self.view.effective_scope()
class BackendProxyingSuite:
"""Benchmark ScopedBackendView search proxying overhead."""
timeout = 60
def setup(self) -> None:
scope = ResourceScope(
resource_ids=frozenset({"R1", "R2"}),
project_names=frozenset({"local/api"}),
)
self.view = ScopedBackendView.from_resource_scope(scope)
self.text_backend = InMemoryTextBackend()
self.vector_backend = InMemoryVectorBackend()
self.graph_backend = InMemoryGraphBackend()
self.embedding: list[float] = [0.1] * 768
def time_search_text(self) -> None:
self.view.search_text(self.text_backend, "auth flow")
def time_search_vector(self) -> None:
self.view.search_vector(self.vector_backend, self.embedding)
def time_search_graph(self) -> None:
self.view.search_graph(
self.graph_backend, "SELECT ?s WHERE { ?s a uko:Container }"
)
# ---------------------------------------------------------------------------
# ScopedBackendSet benchmarks
# ---------------------------------------------------------------------------
class BackendSetSuite:
"""Benchmark ScopedBackendSet creation and multi-backend search."""
timeout = 60
def setup(self) -> None:
self.scope = ResourceScope(
resource_ids=frozenset({"R1"}),
project_names=frozenset({"local/api"}),
)
def time_create_backend_set(self) -> None:
create_scoped_backend_set(
self.scope,
text_backend=InMemoryTextBackend(),
vector_backend=InMemoryVectorBackend(),
graph_backend=InMemoryGraphBackend(),
)
def time_backend_set_text_search(self) -> None:
bset = create_scoped_backend_set(self.scope, text_backend=InMemoryTextBackend())
bset.search_text("query")
# ---------------------------------------------------------------------------
# Scope resolution benchmarks
# ---------------------------------------------------------------------------
class ScopeResolutionSuite:
"""Benchmark resolve_resource_scope and validation guards."""
timeout = 60
def setup(self) -> None:
self.project = NamespacedProject(
name="api",
namespace="local",
linked_resources=[
LinkedResource(resource_id="01HQ8ZDRX50000000000000001", alias="repo"),
LinkedResource(resource_id="01HQ8ZDRX50000000000000002", alias="docs"),
],
)
self.scope = ResourceScope(
resource_ids=frozenset({"R1", "R2", "R3"}),
project_names=frozenset({"p1", "p2"}),
)
def time_resolve_scope_no_filter(self) -> None:
resolve_resource_scope([self.project])
def time_resolve_scope_with_include(self) -> None:
resolve_resource_scope([self.project], include_resources=("repo",))
def time_resolve_scope_with_exclude(self) -> None:
resolve_resource_scope([self.project], exclude_resources=("docs",))
def time_validate_project_scope(self) -> None:
validate_project_scope(frozenset({"p1"}), frozenset({"p1", "p2"}))
def time_validate_resource_scope(self) -> None:
validate_resource_scope(frozenset({"R1"}), self.scope)