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
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
271 lines
8.6 KiB
Python
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)
|