Skip to content

client

arodonata.client

Primary composition root and unified developer facade (ArodonataClient).

Wires ApiPort (AMgmtClient), CachePort (SqlCacheAdapter), and LockPort (SqlLockAdapter) into core domain services (DomainService, DomainSyncCoordinator, ObjectService, ServiceCacheService, AssetRefreshService, SearchService), defaulting engine=None to an auto-owned in-memory SQLite engine (sqlite+aiosqlite:///:memory:).

ArodonataClient

ArodonataClient(
    settings: ArodonataSettings | None = None,
    *,
    engine: AsyncEngine | str | None = None,
    db_manager: DatabaseManager | None = None,
    cache: CachePort | None = None,
    lock_port: LockPort | None = None,
    api: ApiPort | None = None,
    transport: ApiTransport | None = None,
    trust_policy: TrustPolicy | None = None,
    connection_pool: VerifiedConnectionPool | None = None,
    task_waiter: TaskWaiter | None = None,
    rate_limiter: RateLimiter | None = None,
    login_gate: LoginGate | None = None,
    auth_provider: AuthProvider | None = None,
    domain_tracker: DomainListRefreshTracker | None = None,
    cluster_manager: ClusterRelationshipManager | None = None,
    vsx_manager: VSXRelationshipManager | None = None,
    monotonic_fn: Callable[[], float] = time.monotonic,
    now_fn: Callable[[], datetime] = utc_now,
    sleep_fn: Callable[[float], Awaitable[None]] = asyncio.sleep,
    **settings_kwargs: Any,
)

Unified composition root and async facade for Check Point Management & MDSM.

Source code in src/arodonata/client.py
def __init__(
    self,
    settings: ArodonataSettings | None = None,
    *,
    engine: AsyncEngine | str | None = None,
    db_manager: DatabaseManager | None = None,
    cache: CachePort | None = None,
    lock_port: LockPort | None = None,
    api: ApiPort | None = None,
    transport: ApiTransport | None = None,
    trust_policy: TrustPolicy | None = None,
    connection_pool: VerifiedConnectionPool | None = None,
    task_waiter: TaskWaiter | None = None,
    rate_limiter: RateLimiter | None = None,
    login_gate: LoginGate | None = None,
    auth_provider: AuthProvider | None = None,
    domain_tracker: DomainListRefreshTracker | None = None,
    cluster_manager: ClusterRelationshipManager | None = None,
    vsx_manager: VSXRelationshipManager | None = None,
    monotonic_fn: Callable[[], float] = time.monotonic,
    now_fn: Callable[[], datetime] = utc_now,
    sleep_fn: Callable[[float], Awaitable[None]] = asyncio.sleep,
    **settings_kwargs: Any,
) -> None:
    self._settings = settings if settings is not None else ArodonataSettings(**settings_kwargs)
    self._settings.configure_tracing()
    self._now = now_fn

    if db_manager is not None:
        self._db = db_manager
        self._owns_engine = db_manager.owns_engine
    else:
        eff_engine = engine if engine is not None else self._settings.database_url
        self._db = DatabaseManager(
            engine=eff_engine,
            busy_timeout_ms=self._settings.sqlite_busy_timeout_ms,
        )
        self._owns_engine = self._db.owns_engine

    self._cache: CachePort = cache if cache is not None else SqlCacheAdapter(self._db)
    self._lock_port: LockPort = lock_port if lock_port is not None else SqlLockAdapter(self._db)

    if api is not None:
        self._api: ApiPort = api
        self._amgmt: AMgmtClient | None = api if isinstance(api, AMgmtClient) else None
    else:
        self._amgmt = AMgmtClient(
            self._settings,
            db_manager=self._db,
            cache=self._cache,
            lock_port=self._lock_port,
            transport=transport,
            trust_policy=trust_policy,
            connection_pool=connection_pool,
            task_waiter=task_waiter,
            rate_limiter=rate_limiter,
            login_gate=login_gate,
            auth_provider=auth_provider,
            on_domain_invalidated=self._handle_amgmt_domain_invalidated,
            monotonic_fn=monotonic_fn,
            now_fn=now_fn,
            sleep_fn=sleep_fn,
        )
        self._api = self._amgmt

    self._domain_service = DomainService(
        settings=self._settings,
        api=self._api,
        cache=self._cache,
        lock_port=self._lock_port,
        tracker=domain_tracker,
        now_fn=now_fn,
    )
    self._sync_coordinator = DomainSyncCoordinator(
        settings=self._settings,
        api=self._api,
        cache=self._cache,
        lock_port=self._lock_port,
        domain_service=self._domain_service,
        now_fn=now_fn,
    )
    self._object_service = ObjectService(
        settings=self._settings,
        api=self._api,
        cache=self._cache,
        coordinator=self._sync_coordinator,
        now_fn=now_fn,
    )
    self._service_cache = ServiceCacheService(
        settings=self._settings,
        api=self._api,
        cache=self._cache,
        coordinator=self._sync_coordinator,
        now_fn=now_fn,
    )
    self._asset_service = AssetRefreshService(
        settings=self._settings,
        api=self._api,
        cache=self._cache,
        coordinator=self._sync_coordinator,
        cluster_manager=cluster_manager,
        vsx_manager=vsx_manager,
        now_fn=now_fn,
    )
    self._search_service = SearchService(
        cache=self._cache,
        object_service=self._object_service,
        service_cache=self._service_cache,
        asset_service=self._asset_service,
        now_fn=now_fn,
    )
    self._rulebase_refresh = RulebaseRefreshService(
        settings=self._settings,
        api=self._api,
        cache=self._cache,
        coordinator=self._sync_coordinator,
        now_fn=now_fn,
    )
    self._rulebase_source = CachedRulebaseSource(
        cache=self._cache,
        refresh_service=self._rulebase_refresh,
        object_service=self._object_service,
        service_cache=self._service_cache,
        now_fn=now_fn,
    )
    self._cpcrud = CPCRUDService(
        settings=self._settings,
        api=self._api,
        cache=self._cache,
        on_domain_invalidated=self.invalidate_domain,
        ensure_initialized=self._ensure_db_initialized,
        now_fn=now_fn,
    )
    self._warmup_tasks: set[asyncio.Task[Any]] = set()

amgmt property

amgmt: AMgmtClient | None

Underlying AMgmtClient if constructed with the default transport adapter.

api property

api: ApiPort

Active ApiPort adapter.

asset_service property

asset_service: AssetRefreshService

Core AssetRefreshService.

cache property

cache: CachePort

Active CachePort adapter.

cpcrud property

cpcrud: CPCRUDService

Declarative Check Point Policy-as-Code CRUD engine (CPCRUDService).

db property

db: DatabaseManager

Underlying DatabaseManager.

domain_service property

domain_service: DomainService

Core DomainService.

lock_port property

lock_port: LockPort

Active LockPort adapter.

object_service property

object_service: ObjectService

Core ObjectService.

owns_engine property

owns_engine: bool

Return True if this client auto-created and owns its SQLAlchemy AsyncEngine.

rulebase_refresh property

rulebase_refresh: RulebaseRefreshService

Rulebase snapshot refresh service (RulebaseRefreshService).

rulebase_source property

rulebase_source: CachedRulebaseSource

Cached rulebase introspection and numbering source (CachedRulebaseSource).

search_service property

search_service: SearchService

Core SearchService.

service_cache property

service_cache: ServiceCacheService

Core ServiceCacheService.

settings property

settings: ArodonataSettings

Active ArodonataSettings.

sync_coordinator property

sync_coordinator: DomainSyncCoordinator

Core DomainSyncCoordinator.

warmup_tasks property

warmup_tasks: set[Task[Any]]

Currently running background warm-up tasks.

add_object async

add_object(
    mgmt_name: str,
    obj_type: str,
    payload: dict[str, Any],
    *,
    domain: str | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
) -> ApiCallResult

Create a Check Point object (add-<obj_type>) using active WriteSession context, explicit sid, or an automatic isolated write_session (C8).

Source code in src/arodonata/client.py
@traced
async def add_object(
    self,
    mgmt_name: str,
    obj_type: str,
    payload: dict[str, Any],
    *,
    domain: str | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
) -> ApiCallResult:
    """Create a Check Point object (`add-<obj_type>`) using active `WriteSession` context, explicit `sid`, or an automatic isolated `write_session` (`C8`)."""
    clean_type = obj_type.strip().lower().removeprefix("add-")
    span_attrs(mgmt_name=mgmt_name, domain=domain, object_type=clean_type)
    dom_key = normalize_domain_key(domain) if domain is not None else None
    if sid is None and get_write_session(mgmt_name=mgmt_name, domain_name=dom_key) is None:
        async with self.write_session(mgmt_name, domain) as ws:
            return await self.api_call(
                mgmt_name,
                f"add-{clean_type}",
                payload,
                domain=domain,
                sid=ws.sid,
                server_ip=server_ip or ws.server_ip,
            )
    return await self.api_call(
        mgmt_name,
        f"add-{clean_type}",
        payload,
        domain=domain,
        sid=sid,
        server_ip=server_ip,
    )

api_call async

api_call(
    mgmt_name: str,
    command: str,
    payload: dict[str, Any] | None = None,
    *,
    domain: str | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
    wait_for_task: bool = True,
    timeout: float | None = None,
) -> ApiCallResult

Execute a Check Point Management API command, updating active WriteSession overlay and invalidating domain on publish.

Source code in src/arodonata/client.py
@traced
async def api_call(
    self,
    mgmt_name: str,
    command: str,
    payload: dict[str, Any] | None = None,
    *,
    domain: str | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
    wait_for_task: bool = True,
    timeout: float | None = None,
) -> ApiCallResult:
    """Execute a Check Point Management API command, updating active `WriteSession` overlay and invalidating domain on `publish`."""
    if not self._db.is_initialized:
        await self._db.initialize()

    clean_cmd = command.strip().lstrip("/")
    dom_key = normalize_domain_key(domain)
    active_ws = get_write_session(mgmt_name=mgmt_name, domain_name=dom_key if domain is not None else None)

    eff_sid = sid
    eff_ip = server_ip
    if eff_sid is None and active_ws is not None and self._amgmt is None:
        eff_sid = active_ws.sid
        eff_ip = eff_ip or active_ws.server_ip

    res = await self._api.api_call(
        mgmt_name,
        command,
        payload,
        domain=domain,
        sid=eff_sid,
        server_ip=eff_ip,
        wait_for_task=wait_for_task,
        timeout=timeout,
    )

    # If using a custom ApiPort implementation (not AMgmtClient), ensure overlay & invalidation still run
    if self._amgmt is None:
        if active_ws is not None and (sid is None or sid == active_ws.sid):
            _update_overlay_from_command(active_ws.overlay, clean_cmd, payload, res)
        if clean_cmd == "publish" and res.success:
            eff_domain = domain or (active_ws.domain_name if active_ws is not None else None)
            self.invalidate_domain(mgmt_name, eff_domain)
    elif clean_cmd == "publish" and res.success:
        eff_domain = domain or (active_ws.domain_name if active_ws is not None else None)
        self.invalidate_domain(mgmt_name, eff_domain)

    return res

api_query async

api_query(
    mgmt_name: str,
    command: str,
    *,
    domain: str | None = None,
    details_level: str = "standard",
    payload: dict[str, Any] | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
    limit: int = DEFAULT_PAGE_LIMIT,
) -> ApiQueryResult

Execute a continuity-verified paginated Check Point object listing query.

Source code in src/arodonata/client.py
@traced
async def api_query(
    self,
    mgmt_name: str,
    command: str,
    *,
    domain: str | None = None,
    details_level: str = "standard",
    payload: dict[str, Any] | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
    limit: int = DEFAULT_PAGE_LIMIT,
) -> ApiQueryResult:
    """Execute a continuity-verified paginated Check Point object listing query."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._api.api_query(
        mgmt_name,
        command,
        domain=domain,
        details_level=details_level,
        payload=payload,
        sid=sid,
        server_ip=server_ip,
        limit=limit,
    )

auth_context

auth_context(
    auth: AuthContext | None = None,
    *,
    username: str | None = None,
    password: str | None = None,
    api_key: str | None = None,
    user_id: str | None = None,
) -> Iterator[AuthContext]

Bind runtime administrator credentials (AuthContext) in contextvars.

Source code in src/arodonata/client.py
@contextmanager
def auth_context(
    self,
    auth: AuthContext | None = None,
    *,
    username: str | None = None,
    password: str | None = None,
    api_key: str | None = None,
    user_id: str | None = None,
) -> Iterator[AuthContext]:
    """Bind runtime administrator credentials (`AuthContext`) in `contextvars`."""
    with _ctx_auth_context(
        auth,
        username=username,
        password=password,
        api_key=api_key,
        user_id=user_id,
    ) as resolved:
        yield resolved

build_change_report async

build_change_report(
    scopes: Sequence[ReportScope | dict[str, Any]],
    *,
    formats: Sequence[str] = ("html", "markdown", "json"),
    options: RenderOptions | None = None,
    max_uid_lookups: int = MAX_UID_LOOKUPS,
) -> RenderedReportBundle

Collect and render a ChangeReport across scopes into formats.

Source code in src/arodonata/client.py
@traced
async def build_change_report(
    self,
    scopes: Sequence[ReportScope | dict[str, Any]],
    *,
    formats: Sequence[str] = ("html", "markdown", "json"),
    options: RenderOptions | None = None,
    max_uid_lookups: int = MAX_UID_LOOKUPS,
) -> RenderedReportBundle:
    """Collect and render a `ChangeReport` across `scopes` into `formats`."""
    if not self._db.is_initialized:
        await self._db.initialize()

    async def _warm_rulebase(m_name: str, d_name: str) -> Any:
        return await self.refresh_rulebases(
            mgmt_name=m_name,
            domains=[d_name] if d_name else None,
            mode="smart",
        )

    return await _build_change_report(
        self._api,
        self._cache,
        scopes,
        formats=formats,
        options=options,
        max_uid_lookups=max_uid_lookups,
        now_fn=self._now,
        refresh_rulebase_fn=_warm_rulebase,
    )

close async

close(*, logout: bool | None = None) -> None

Cancel running background warm-up tasks immediately, drain startup cleanup, close transport, and dispose owned engine.

Source code in src/arodonata/client.py
@traced
async def close(self, *, logout: bool | None = None) -> None:
    """Cancel running background warm-up tasks immediately, drain startup cleanup, close transport, and dispose owned engine."""
    # 1. Cancel any running background warm-up tasks immediately
    active_warmups = [t for t in self._warmup_tasks if not t.done()]
    for task in active_warmups:
        task.cancel()
    if active_warmups:
        await asyncio.gather(*active_warmups, return_exceptions=True)
    self._warmup_tasks.clear()

    # 2. Determine automatic logout policy: True if client auto-created in-memory DB, False if persistent DB or external engine
    is_ephemeral_db = self._owns_engine and ":memory:" in (self._settings.database_url or "")
    should_logout = is_ephemeral_db if logout is None else bool(logout)

    # 3. Close AMgmtClient / ApiPort (drains startup cleanup tasks & logs out according to policy)
    await self._api.close(logout=should_logout)

    # 4. Dispose SQLAlchemy engine only if owned (`self._db.close()` checks `owns_engine`)
    await self._db.close()

collect_change_report async

collect_change_report(
    scopes: Sequence[ReportScope | dict[str, Any]],
    *,
    options: RenderOptions | None = None,
    max_uid_lookups: int = MAX_UID_LOOKUPS,
) -> ChangeReport

Collect read-only auditable session change evidence (ChangeReport) across scopes.

Source code in src/arodonata/client.py
@traced
async def collect_change_report(
    self,
    scopes: Sequence[ReportScope | dict[str, Any]],
    *,
    options: RenderOptions | None = None,
    max_uid_lookups: int = MAX_UID_LOOKUPS,
) -> ChangeReport:
    """Collect read-only auditable session change evidence (`ChangeReport`) across `scopes`."""
    if not self._db.is_initialized:
        await self._db.initialize()

    async def _warm_rulebase(m_name: str, d_name: str) -> Any:
        return await self.refresh_rulebases(
            mgmt_name=m_name,
            domains=[d_name] if d_name else None,
            mode="smart",
        )

    return await _collect_change_report(
        self._api,
        self._cache,
        scopes,
        options=options,
        max_uid_lookups=max_uid_lookups,
        now_fn=self._now,
        refresh_rulebase_fn=_warm_rulebase,
    )

create_dedicated_session async

create_dedicated_session(
    mgmt_name: str,
    domain: str | None = None,
    *,
    auth: AuthContext | None = None,
    session_name: str | None = None,
    session_description: str | None = None,
) -> tuple[str, str]

Open an isolated write session outside sid_cache, returning (sid, server_ip).

Source code in src/arodonata/client.py
async def create_dedicated_session(
    self,
    mgmt_name: str,
    domain: str | None = None,
    *,
    auth: AuthContext | None = None,
    session_name: str | None = None,
    session_description: str | None = None,
) -> tuple[str, str]:
    """Open an isolated write session outside `sid_cache`, returning `(sid, server_ip)`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._api.create_dedicated_session(
        mgmt_name,
        domain,
        auth=auth,
        session_name=session_name,
        session_description=session_description,
    )

delete_object async

delete_object(
    mgmt_name: str,
    obj_type: str,
    *,
    uid: str | None = None,
    name: str | None = None,
    payload: dict[str, Any] | None = None,
    domain: str | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
) -> ApiCallResult

Delete a Check Point object (delete-<obj_type>) by uid or name using active WriteSession, explicit sid, or automatic isolated write_session (C8).

Source code in src/arodonata/client.py
@traced
async def delete_object(
    self,
    mgmt_name: str,
    obj_type: str,
    *,
    uid: str | None = None,
    name: str | None = None,
    payload: dict[str, Any] | None = None,
    domain: str | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
) -> ApiCallResult:
    """Delete a Check Point object (`delete-<obj_type>`) by `uid` or `name` using active `WriteSession`, explicit `sid`, or automatic isolated `write_session` (`C8`)."""
    clean_type = obj_type.strip().lower().removeprefix("delete-")
    req_payload: dict[str, Any] = dict(payload or {})
    if uid is not None:
        req_payload["uid"] = uid
    if name is not None:
        req_payload["name"] = name
    span_attrs(mgmt_name=mgmt_name, domain=domain, object_type=clean_type)
    dom_key = normalize_domain_key(domain) if domain is not None else None
    if sid is None and get_write_session(mgmt_name=mgmt_name, domain_name=dom_key) is None:
        async with self.write_session(mgmt_name, domain) as ws:
            return await self.api_call(
                mgmt_name,
                f"delete-{clean_type}",
                req_payload,
                domain=domain,
                sid=ws.sid,
                server_ip=server_ip or ws.server_ip,
            )
    return await self.api_call(
        mgmt_name,
        f"delete-{clean_type}",
        req_payload,
        domain=domain,
        sid=sid,
        server_ip=server_ip,
    )

describe_objects async

describe_objects(
    *,
    mgmt_name: str | None = None,
    domain_name: str | None = None,
    include_global: bool = True,
) -> dict[str, Any]

Return progressive-disclosure schema and summary counts for cached objects and services.

Source code in src/arodonata/client.py
@traced
async def describe_objects(
    self,
    *,
    mgmt_name: str | None = None,
    domain_name: str | None = None,
    include_global: bool = True,
) -> dict[str, Any]:
    """Return progressive-disclosure schema and summary counts for cached objects and services."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._search_service.describe_objects(
        mgmt_name=mgmt_name,
        domain_name=domain_name,
        include_global=include_global,
    )

discard_staged_session async

discard_staged_session(
    mgmt_name: str,
    domain: str | None = None,
    *,
    session_uid: str,
    sid: str | None = None,
    server_ip: str | None = None,
    auth: AuthContext | None = None,
    force_disconnect: bool = False,
) -> ApiCallResult

Adopt session_uid, discard uncommitted changes, and log out.

Source code in src/arodonata/client.py
@traced
async def discard_staged_session(
    self,
    mgmt_name: str,
    domain: str | None = None,
    *,
    session_uid: str,
    sid: str | None = None,
    server_ip: str | None = None,
    auth: AuthContext | None = None,
    force_disconnect: bool = False,
) -> ApiCallResult:
    """Adopt `session_uid`, discard uncommitted changes, and log out."""
    return await self._cpcrud.discard_staged_session(
        mgmt_name,
        domain,
        session_uid=session_uid,
        sid=sid,
        server_ip=server_ip,
        auth=auth,
        force_disconnect=force_disconnect,
    )

filter_rulebase async

filter_rulebase(
    mgmt_name: str,
    domain_name: str | None = None,
    *,
    package: str | None = None,
    layer: str | None = None,
    rulebase_type: RulebaseType = "access",
    filter_query: str = "",
    output_format: RuleFormat = "model_friendly",
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> FilteredRulebaseResult

Evaluate filter_query locally against cached rulebase and cached objects/services.

Source code in src/arodonata/client.py
@traced
async def filter_rulebase(
    self,
    mgmt_name: str,
    domain_name: str | None = None,
    *,
    package: str | None = None,
    layer: str | None = None,
    rulebase_type: RulebaseType = "access",
    filter_query: str = "",
    output_format: RuleFormat = "model_friendly",
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> FilteredRulebaseResult:
    """Evaluate `filter_query` locally against cached rulebase and cached objects/services."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._rulebase_source.filter_rulebase(
        mgmt_name,
        domain_name,
        package=package,
        layer=layer,
        rulebase_type=rulebase_type,
        filter_query=filter_query,
        output_format=output_format,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

find_object async

find_object(
    mgmt_name: str,
    domain: str | None = None,
    name_or_uid: str = "",
    *,
    obj_type: str | None = None,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> CPObject | CPService | Asset | None

Find a single CPObject, CPService, or Asset by exact uid or name in (mgmt_name, domain) (or across all domains when domain is None), including active WriteSession overlay.

Source code in src/arodonata/client.py
@traced
async def find_object(
    self,
    mgmt_name: str,
    domain: str | None = None,
    name_or_uid: str = "",
    *,
    obj_type: str | None = None,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> CPObject | CPService | Asset | None:
    """Find a single `CPObject`, `CPService`, or `Asset` by exact `uid` or `name` in `(mgmt_name, domain)` (or across all domains when `domain` is `None`), including active `WriteSession` overlay."""
    clean_query = name_or_uid.strip() if name_or_uid else ""
    if not clean_query:
        raise ValidationError("name_or_uid must not be empty or whitespace")

    if not self._db.is_initialized:
        await self._db.initialize()

    clean_type = normalize_any_type(obj_type) if obj_type else None
    is_service_type = clean_type is not None and (
        clean_type in SERVICE_COMMAND_TYPES or clean_type.startswith("service-")
    )
    is_asset_type = clean_type is not None and clean_type in {t.lower() for t in CANONICAL_ASSET_TYPES}

    eff_domain = domain.strip() if domain and domain.strip() else None
    lookup_key = clean_query
    if eff_domain:
        composite_prefix = f"{mgmt_name}:{eff_domain}:"
        if clean_query.startswith(composite_prefix):
            lookup_key = clean_query.removeprefix(composite_prefix)
    elif clean_query.startswith(f"{mgmt_name}:"):
        parts = clean_query.split(":", 2)
        if len(parts) == 3 and parts[1].strip() and parts[2].strip():
            eff_domain = parts[1].strip()
            lookup_key = parts[2].strip()
    lookup_names = list(dict.fromkeys([clean_query, lookup_key]))

    if eff_domain is not None:
        if not is_service_type and not is_asset_type:
            by_uid = await self._object_service.get_object_by_uid(
                mgmt_name,
                eff_domain,
                lookup_key,
                cache_mode=cache_mode,
                cache_ttl=cache_ttl,
            )
            if by_uid is not None and (clean_type is None or by_uid.type.lower() == clean_type):
                return by_uid

            objs = await self._object_service.get_objects(
                mgmt_name=mgmt_name,
                domain=eff_domain,
                types=[clean_type] if clean_type else None,
                names=lookup_names,
                include_global=(eff_domain == "Global"),
                cache_mode="cache" if cache_mode != "force" else cache_mode,
                cache_ttl=cache_ttl,
            )
            if objs:
                return objs[0]

        if is_service_type or clean_type is None:
            svc_by_uid = await self._service_cache.get_service_by_uid(
                mgmt_name,
                eff_domain,
                lookup_key,
                cache_mode=cache_mode,
                cache_ttl=cache_ttl,
            )
            if svc_by_uid is not None and (clean_type is None or svc_by_uid.type.lower() == clean_type):
                return svc_by_uid

            svcs = await self._service_cache.get_services(
                mgmt_name=mgmt_name,
                domain=eff_domain,
                types=[clean_type] if clean_type else None,
                names=lookup_names,
                include_global=(eff_domain == "Global"),
                cache_mode="cache" if cache_mode != "force" else cache_mode,
                cache_ttl=cache_ttl,
            )
            if svcs:
                return svcs[0]

        if is_asset_type or clean_type is None:
            assets = await self._asset_service.get_assets(
                mgmt_name=mgmt_name,
                domain=eff_domain,
                types=[clean_type] if clean_type else None,
                include_global=(eff_domain == "Global"),
                cache_mode=cache_mode,
                cache_ttl=cache_ttl,
            )
            needle_lower = clean_query.lower()
            lookup_lower = lookup_key.lower()
            for asset in assets:
                if (
                    asset.uid in (clean_query, lookup_key)
                    or asset.name in (clean_query, lookup_key)
                    or asset.asset_id == clean_query
                    or asset.id == clean_query
                ):
                    return asset
            for asset in assets:
                if (
                    asset.name.lower() in (needle_lower, lookup_lower)
                    or asset.asset_id.lower() == needle_lower
                    or asset.id.lower() == needle_lower
                ):
                    return asset

        return None

    # Cross-domain lookup (eff_domain is None): strictly cache-only and includes Global
    needle_lower = clean_query.lower()
    lookup_lower = lookup_key.lower()
    exact_matches: list[CPObject | CPService | Asset] = []
    ci_matches: list[CPObject | CPService | Asset] = []

    if not is_service_type and not is_asset_type:
        objs = await self._object_service.get_objects(
            mgmt_name=mgmt_name,
            domain=None,
            types=[clean_type] if clean_type else None,
            names=None,
            include_global=True,
            cache_mode="cache",
            cache_ttl=cache_ttl,
        )
        for o in objs:
            if (
                o.uid in (clean_query, lookup_key)
                or o.name in (clean_query, lookup_key)
                or o.object_id == clean_query
                or o.id == clean_query
            ):
                exact_matches.append(o)
            elif (
                o.uid.lower() in (needle_lower, lookup_lower)
                or o.name.lower() in (needle_lower, lookup_lower)
                or o.object_id.lower() == needle_lower
                or o.id.lower() == needle_lower
            ):
                ci_matches.append(o)

    if is_service_type or clean_type is None:
        svcs = await self._service_cache.get_services(
            mgmt_name=mgmt_name,
            domain=None,
            types=[clean_type] if clean_type else None,
            names=None,
            include_global=True,
            cache_mode="cache",
            cache_ttl=cache_ttl,
        )
        for s in svcs:
            if (
                s.uid in (clean_query, lookup_key)
                or s.name in (clean_query, lookup_key)
                or s.service_id == clean_query
                or s.id == clean_query
            ):
                exact_matches.append(s)
            elif (
                s.uid.lower() in (needle_lower, lookup_lower)
                or s.name.lower() in (needle_lower, lookup_lower)
                or s.service_id.lower() == needle_lower
                or s.id.lower() == needle_lower
            ):
                ci_matches.append(s)

    if is_asset_type or clean_type is None:
        assets = await self._asset_service.get_assets(
            mgmt_name=mgmt_name,
            domain=None,
            types=[clean_type] if clean_type else None,
            include_global=True,
            cache_mode="cache",
            cache_ttl=cache_ttl,
        )
        for asset in assets:
            if (
                asset.uid in (clean_query, lookup_key)
                or asset.name in (clean_query, lookup_key)
                or asset.asset_id == clean_query
                or asset.id == clean_query
            ):
                exact_matches.append(asset)
            elif (
                asset.name.lower() in (needle_lower, lookup_lower)
                or asset.asset_id.lower() == needle_lower
                or asset.id.lower() == needle_lower
            ):
                ci_matches.append(asset)

    chosen_pool = exact_matches if exact_matches else ci_matches
    if not chosen_pool:
        return None

    deduped: list[CPObject | CPService | Asset] = []
    seen_keys: set[tuple[str, str]] = set()
    for item in chosen_pool:
        key = (item.domain_name, item.uid)
        if key not in seen_keys:
            seen_keys.add(key)
            deduped.append(item)

    distinct_domains = {item.domain_name for item in deduped}
    if len(distinct_domains) > 1:
        raise AmbiguousObjectError(
            mgmt_name,
            clean_query,
            [item.model_dump(mode="json") for item in deduped],
        )
    return deduped[0]

get_access_rules async

get_access_rules(
    mgmt_name: str,
    domain: str,
    *,
    layer_uid: str | None = None,
    layer_name: str | None = None,
) -> list[AccessRuleEntry]

Return cached access-control rules for (mgmt_name, domain).

Source code in src/arodonata/client.py
@traced
async def get_access_rules(
    self,
    mgmt_name: str,
    domain: str,
    *,
    layer_uid: str | None = None,
    layer_name: str | None = None,
) -> list[AccessRuleEntry]:
    """Return cached access-control rules for `(mgmt_name, domain)`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    snapshot = await self._cache.get_domain_rulebase(mgmt_name, domain)
    rules = snapshot.access_rules
    if layer_uid is not None:
        rules = [r for r in rules if r.layer_uid == layer_uid]
    if layer_name is not None:
        rules = [r for r in rules if r.layer_name == layer_name]
    return rules

get_address_ranges async

get_address_ranges(
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]

Return cached address-range CPObject models, merging any active WriteSession overlay.

Source code in src/arodonata/client.py
@traced
async def get_address_ranges(
    self,
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]:
    """Return cached `address-range` `CPObject` models, merging any active `WriteSession` overlay."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._object_service.get_objects(
        mgmt_name=mgmt_name,
        domain=domain,
        types=["address-range"],
        names=names,
        uids=uids,
        include_global=include_global,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_cache_status async

get_cache_status(
    *,
    mgmt_name: str | None = None,
    domain_name: str | None = None,
    scope: SyncScope | None = None,
) -> dict[str, Any]

Inspect domain_sync_state across scopes and return per-domain/per-type freshness and cache_age_seconds.

Source code in src/arodonata/client.py
@traced
async def get_cache_status(
    self,
    *,
    mgmt_name: str | None = None,
    domain_name: str | None = None,
    scope: SyncScope | None = None,
) -> dict[str, Any]:
    """Inspect `domain_sync_state` across scopes and return per-domain/per-type freshness and `cache_age_seconds`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._search_service.get_cache_status(
        mgmt_name=mgmt_name,
        domain_name=domain_name,
        scope=scope,
    )

get_dns_domains async

get_dns_domains(
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]

Return cached dns-domain CPObject models, merging any active WriteSession overlay.

Source code in src/arodonata/client.py
@traced
async def get_dns_domains(
    self,
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]:
    """Return cached `dns-domain` `CPObject` models, merging any active `WriteSession` overlay."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._object_service.get_objects(
        mgmt_name=mgmt_name,
        domain=domain,
        types=["dns-domain"],
        names=names,
        uids=uids,
        include_global=include_global,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_domains async

get_domains(
    mgmt_name: str | None = None,
    *,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[DomainInfo]

Return discovered domains, excluding Global unless include_global=True.

Never triggers implicit background object-cache warm-up.

Source code in src/arodonata/client.py
@traced
async def get_domains(
    self,
    mgmt_name: str | None = None,
    *,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[DomainInfo]:
    """Return discovered domains, excluding `Global` unless `include_global=True`.

    Never triggers implicit background object-cache warm-up.
    """
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._domain_service.get_domains(
        mgmt_name=mgmt_name,
        include_global=include_global,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_gateways async

get_gateways(
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[Asset]

Return cached gateway, cluster, VSX, and server Asset models.

Source code in src/arodonata/client.py
@traced
async def get_gateways(
    self,
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[Asset]:
    """Return cached gateway, cluster, VSX, and server `Asset` models."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._asset_service.get_assets(
        mgmt_name=mgmt_name,
        domain=domain,
        types=types,
        include_global=include_global,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_group_members async

get_group_members(
    mgmt_name: str,
    domain: str | None = None,
    group_name_or_uid: str = "",
    *,
    recursive: bool = True,
    max_depth: int = 5,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject | CPService | Asset]

Resolve direct (and optionally recursive) member CPObject, CPService, or child Asset items for a group, service-group, cluster, or VSX container in (mgmt_name, domain).

Source code in src/arodonata/client.py
@traced
async def get_group_members(
    self,
    mgmt_name: str,
    domain: str | None = None,
    group_name_or_uid: str = "",
    *,
    recursive: bool = True,
    max_depth: int = 5,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject | CPService | Asset]:
    """Resolve direct (and optionally recursive) member `CPObject`, `CPService`, or child `Asset` items for a group, service-group, cluster, or VSX container in `(mgmt_name, domain)`."""
    if not group_name_or_uid or not group_name_or_uid.strip():
        raise ValidationError("group_name_or_uid must not be empty or whitespace")
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._search_service.get_group_members(
        mgmt_name,
        domain,
        group_name_or_uid,
        recursive=recursive,
        max_depth=max_depth,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_groups async

get_groups(
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]

Return cached group CPObject models, merging any active WriteSession overlay.

Source code in src/arodonata/client.py
@traced
async def get_groups(
    self,
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]:
    """Return cached `group` `CPObject` models, merging any active `WriteSession` overlay."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._object_service.get_objects(
        mgmt_name=mgmt_name,
        domain=domain,
        types=["group"],
        names=names,
        uids=uids,
        include_global=include_global,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_hosts async

get_hosts(
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]

Return cached host CPObject models, merging any active WriteSession overlay.

Source code in src/arodonata/client.py
@traced
async def get_hosts(
    self,
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]:
    """Return cached `host` `CPObject` models, merging any active `WriteSession` overlay."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._object_service.get_objects(
        mgmt_name=mgmt_name,
        domain=domain,
        types=["host"],
        names=names,
        uids=uids,
        include_global=include_global,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_https_rules async

get_https_rules(
    mgmt_name: str,
    domain: str,
    *,
    layer_uid: str | None = None,
    layer_name: str | None = None,
) -> list[HttpsRuleEntry]

Return cached HTTPS inspection rules for (mgmt_name, domain).

Source code in src/arodonata/client.py
@traced
async def get_https_rules(
    self,
    mgmt_name: str,
    domain: str,
    *,
    layer_uid: str | None = None,
    layer_name: str | None = None,
) -> list[HttpsRuleEntry]:
    """Return cached HTTPS inspection rules for `(mgmt_name, domain)`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    snapshot = await self._cache.get_domain_rulebase(mgmt_name, domain)
    rules = snapshot.https_rules
    if layer_uid is not None:
        rules = [r for r in rules if r.layer_uid == layer_uid]
    if layer_name is not None:
        rules = [r for r in rules if r.layer_name == layer_name]
    return rules

get_layer_rulebase async

get_layer_rulebase(
    mgmt_name: str,
    domain_name: str | None,
    layer: str,
    *,
    rulebase_type: RulebaseType | None = None,
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> NumberedLayerRulebase

Return a SmartConsole-numbered NumberedLayerRulebase for layer in (mgmt_name, domain_name).

Source code in src/arodonata/client.py
@traced
async def get_layer_rulebase(
    self,
    mgmt_name: str,
    domain_name: str | None,
    layer: str,
    *,
    rulebase_type: RulebaseType | None = None,
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> NumberedLayerRulebase:
    """Return a SmartConsole-numbered `NumberedLayerRulebase` for `layer` in `(mgmt_name, domain_name)`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._rulebase_source.get_layer_rulebase(
        mgmt_name,
        domain_name,
        layer,
        rulebase_type=rulebase_type,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_nat_rules async

get_nat_rules(
    mgmt_name: str,
    domain: str,
    *,
    package_uid: str | None = None,
    package_name: str | None = None,
) -> list[NatRuleEntry]

Return cached NAT rules for (mgmt_name, domain).

Source code in src/arodonata/client.py
@traced
async def get_nat_rules(
    self,
    mgmt_name: str,
    domain: str,
    *,
    package_uid: str | None = None,
    package_name: str | None = None,
) -> list[NatRuleEntry]:
    """Return cached NAT rules for `(mgmt_name, domain)`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    snapshot = await self._cache.get_domain_rulebase(mgmt_name, domain)
    rules = snapshot.nat_rules
    if package_uid is not None:
        rules = [r for r in rules if r.package_uid == package_uid]
    if package_name is not None:
        rules = [r for r in rules if r.package_name == package_name]
    return rules

get_networks async

get_networks(
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]

Return cached network CPObject models, merging any active WriteSession overlay.

Source code in src/arodonata/client.py
@traced
async def get_networks(
    self,
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]:
    """Return cached `network` `CPObject` models, merging any active `WriteSession` overlay."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._object_service.get_objects(
        mgmt_name=mgmt_name,
        domain=domain,
        types=["network"],
        names=names,
        uids=uids,
        include_global=include_global,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_object_by_uid async

get_object_by_uid(
    mgmt_name: str,
    domain: str,
    uid: str,
    *,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> CPObject | None

Return a single CPObject by (mgmt_name, domain, uid), respecting any active WriteSession overlay.

Source code in src/arodonata/client.py
@traced
async def get_object_by_uid(
    self,
    mgmt_name: str,
    domain: str,
    uid: str,
    *,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> CPObject | None:
    """Return a single `CPObject` by `(mgmt_name, domain, uid)`, respecting any active `WriteSession` overlay."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._object_service.get_object_by_uid(
        mgmt_name,
        domain,
        uid,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_objects async

get_objects(
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    types: Sequence[str] | None = None,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]

Return cached network CPObject models across types, merging any active WriteSession overlay.

Source code in src/arodonata/client.py
@traced
async def get_objects(
    self,
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    types: Sequence[str] | None = None,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPObject]:
    """Return cached network `CPObject` models across `types`, merging any active `WriteSession` overlay."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._object_service.get_objects(
        mgmt_name=mgmt_name,
        domain=domain,
        types=types,
        names=names,
        uids=uids,
        include_global=include_global,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_package_rulebase async

get_package_rulebase(
    mgmt_name: str,
    domain_name: str | None,
    package: str,
    *,
    rulebase_type: RulebaseType = "access",
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> PackageRulebase

Return a SmartConsole-numbered PackageRulebase for package in (mgmt_name, domain_name).

Source code in src/arodonata/client.py
@traced
async def get_package_rulebase(
    self,
    mgmt_name: str,
    domain_name: str | None,
    package: str,
    *,
    rulebase_type: RulebaseType = "access",
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> PackageRulebase:
    """Return a SmartConsole-numbered `PackageRulebase` for `package` in `(mgmt_name, domain_name)`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._rulebase_source.get_package_rulebase(
        mgmt_name,
        domain_name,
        package,
        rulebase_type=rulebase_type,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_policy_packages async

get_policy_packages(
    mgmt_name: str,
    domain_name: str | None = None,
    *,
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> list[PolicyPackageInfo]

Return cached PolicyPackageInfo entries for (mgmt_name, domain_name).

Source code in src/arodonata/client.py
@traced
async def get_policy_packages(
    self,
    mgmt_name: str,
    domain_name: str | None = None,
    *,
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> list[PolicyPackageInfo]:
    """Return cached `PolicyPackageInfo` entries for `(mgmt_name, domain_name)`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._rulebase_source.get_policy_packages(
        mgmt_name,
        domain_name,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_services async

get_services(
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    types: Sequence[str] | None = None,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPService]

Return cached CPService models, merging any active WriteSession overlay.

Source code in src/arodonata/client.py
@traced
async def get_services(
    self,
    mgmt_name: str | None = None,
    domain: str | None = None,
    *,
    types: Sequence[str] | None = None,
    names: Sequence[str] | None = None,
    uids: Sequence[str] | None = None,
    include_global: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> list[CPService]:
    """Return cached `CPService` models, merging any active `WriteSession` overlay."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._service_cache.get_services(
        mgmt_name=mgmt_name,
        domain=domain,
        types=types,
        names=names,
        uids=uids,
        include_global=include_global,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

get_threat_rules async

get_threat_rules(
    mgmt_name: str,
    domain: str,
    *,
    layer_uid: str | None = None,
    layer_name: str | None = None,
) -> list[ThreatRuleEntry]

Return cached Threat Prevention rules for (mgmt_name, domain).

Source code in src/arodonata/client.py
@traced
async def get_threat_rules(
    self,
    mgmt_name: str,
    domain: str,
    *,
    layer_uid: str | None = None,
    layer_name: str | None = None,
) -> list[ThreatRuleEntry]:
    """Return cached Threat Prevention rules for `(mgmt_name, domain)`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    snapshot = await self._cache.get_domain_rulebase(mgmt_name, domain)
    rules = snapshot.threat_rules
    if layer_uid is not None:
        rules = [r for r in rules if r.layer_uid == layer_uid]
    if layer_name is not None:
        rules = [r for r in rules if r.layer_name == layer_name]
    return rules

initialize async

initialize(*, run_startup_cleanup: bool = True) -> None

Initialize the database schema and optionally schedule startup session cleanup.

Source code in src/arodonata/client.py
async def initialize(self, *, run_startup_cleanup: bool = True) -> None:
    """Initialize the database schema and optionally schedule startup session cleanup."""
    if not self._db.is_initialized:
        await self._db.initialize()
    if self._amgmt is not None:
        await self._amgmt.initialize(run_startup_cleanup=run_startup_cleanup)

invalidate_domain

invalidate_domain(mgmt_name: str, domain: str | None = None) -> None

Invalidate freshness memos for (mgmt_name, domain) so the next smart read re-checks published head.

Source code in src/arodonata/client.py
def invalidate_domain(self, mgmt_name: str, domain: str | None = None) -> None:
    """Invalidate freshness memos for `(mgmt_name, domain)` so the next smart read re-checks published head."""
    self._sync_coordinator.invalidate_domain(mgmt_name, domain)
    if self._amgmt is not None and (mgmt_name, normalize_domain_key(domain)) not in self._amgmt.invalidated_domains:
        self._amgmt._invalidated_domains.add((mgmt_name, normalize_domain_key(domain)))

locate_rules async

locate_rules(
    mgmt_name: str,
    domain_name: str | None = None,
    *,
    rule_uids: Sequence[str] | None = None,
    layer_uids: Sequence[str] | None = None,
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> RuleLocations

Locate SmartConsole rule numbers and layer prefixes across all packages and layers of a domain.

Source code in src/arodonata/client.py
@traced
async def locate_rules(
    self,
    mgmt_name: str,
    domain_name: str | None = None,
    *,
    rule_uids: Sequence[str] | None = None,
    layer_uids: Sequence[str] | None = None,
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> RuleLocations:
    """Locate SmartConsole rule numbers and layer prefixes across all packages and layers of a domain."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._rulebase_source.locate_rules(
        mgmt_name,
        domain_name,
        rule_uids=rule_uids,
        layer_uids=layer_uids,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

logout_sid

logout_sid(
    mgmt_name: str, sid: str, *, server_ip: str | None = None
) -> Awaitable[bool]

Log out a specific session identifier.

Source code in src/arodonata/client.py
def logout_sid(
    self,
    mgmt_name: str,
    sid: str,
    *,
    server_ip: str | None = None,
) -> Awaitable[bool]:
    """Log out a specific session identifier."""
    return self._api.logout_sid(mgmt_name, sid, server_ip=server_ip)

publish_staged_session async

publish_staged_session(
    mgmt_name: str,
    domain: str | None = None,
    *,
    session_uid: str,
    sid: str | None = None,
    server_ip: str | None = None,
    timeout: float | None = None,
    auth: AuthContext | None = None,
    force_disconnect: bool = False,
) -> ApiCallResult

Adopt session_uid, publish it, log out, and invalidate the domain cache.

Source code in src/arodonata/client.py
@traced
async def publish_staged_session(
    self,
    mgmt_name: str,
    domain: str | None = None,
    *,
    session_uid: str,
    sid: str | None = None,
    server_ip: str | None = None,
    timeout: float | None = None,
    auth: AuthContext | None = None,
    force_disconnect: bool = False,
) -> ApiCallResult:
    """Adopt `session_uid`, publish it, log out, and invalidate the domain cache."""
    return await self._cpcrud.publish_staged_session(
        mgmt_name,
        domain,
        session_uid=session_uid,
        sid=sid,
        server_ip=server_ip,
        timeout=timeout,
        auth=auth,
        force_disconnect=force_disconnect,
    )

query_objects async

query_objects(
    *,
    mgmt_name: str | None = None,
    domain_name: str | None = None,
    domains: Sequence[str] | None = None,
    types: Sequence[str] | None = None,
    scope_origin: Literal["all", "local", "global"] = "all",
    name_pattern: str | None = None,
    ip_subnet: str | None = None,
    port: str | None = None,
    protocol: str | None = None,
    tags: Sequence[str] | None = None,
    include_global: bool = False,
    group_by: Sequence[str] | None = None,
    aggregate: Literal["count", "list", "describe"] = "list",
    limit: int = 100,
    offset: int = 0,
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> dict[str, Any]

Execute structured cross-domain filtering, grouping, and counting over cached objects and services.

Source code in src/arodonata/client.py
@traced
async def query_objects(
    self,
    *,
    mgmt_name: str | None = None,
    domain_name: str | None = None,
    domains: Sequence[str] | None = None,
    types: Sequence[str] | None = None,
    scope_origin: Literal["all", "local", "global"] = "all",
    name_pattern: str | None = None,
    ip_subnet: str | None = None,
    port: str | None = None,
    protocol: str | None = None,
    tags: Sequence[str] | None = None,
    include_global: bool = False,
    group_by: Sequence[str] | None = None,
    aggregate: Literal["count", "list", "describe"] = "list",
    limit: int = 100,
    offset: int = 0,
    cache_mode: CacheMode = "cache",
    cache_ttl: float | None = None,
) -> dict[str, Any]:
    """Execute structured cross-domain filtering, grouping, and counting over cached objects and services."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._search_service.query_objects(
        mgmt_name=mgmt_name,
        domain_name=domain_name,
        domains=domains,
        types=types,
        scope_origin=scope_origin,
        name_pattern=name_pattern,
        ip_subnet=ip_subnet,
        port=port,
        protocol=protocol,
        tags=tags,
        include_global=include_global,
        group_by=group_by,
        aggregate=aggregate,
        limit=limit,
        offset=offset,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

refresh_assets async

refresh_assets(
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> RefreshOutcome

Synchronize gateway, cluster, VSX, and server assets across domains and return RefreshOutcome.

Source code in src/arodonata/client.py
@traced
async def refresh_assets(
    self,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> RefreshOutcome:
    """Synchronize gateway, cluster, VSX, and server assets across domains and return `RefreshOutcome`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._asset_service.refresh_assets(
        mgmt_name=mgmt_name,
        domains=domains,
        include_global=include_global,
        mode=mode,
        cache_ttl=cache_ttl,
    )

refresh_objects async

refresh_objects(
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> RefreshOutcome

Synchronize network objects across target domains and return RefreshOutcome.

Source code in src/arodonata/client.py
@traced
async def refresh_objects(
    self,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> RefreshOutcome:
    """Synchronize network objects across target domains and return `RefreshOutcome`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._object_service.refresh_objects(
        mgmt_name=mgmt_name,
        domains=domains,
        types=types,
        include_global=include_global,
        mode=mode,
        cache_ttl=cache_ttl,
    )

refresh_rulebases async

refresh_rulebases(
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> RefreshOutcome

Synchronize policy rulebase snapshots across target domains and return RefreshOutcome.

Source code in src/arodonata/client.py
@traced
async def refresh_rulebases(
    self,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> RefreshOutcome:
    """Synchronize policy rulebase snapshots across target domains and return `RefreshOutcome`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._rulebase_refresh.refresh_rulebases(
        mgmt_name=mgmt_name,
        domains=domains,
        include_global=include_global,
        mode=mode,
        cache_ttl=cache_ttl,
    )

refresh_services async

refresh_services(
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> RefreshOutcome

Synchronize service objects across target domains and return RefreshOutcome.

Source code in src/arodonata/client.py
@traced
async def refresh_services(
    self,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> RefreshOutcome:
    """Synchronize service objects across target domains and return `RefreshOutcome`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._service_cache.refresh_services(
        mgmt_name=mgmt_name,
        domains=domains,
        types=types,
        include_global=include_global,
        mode=mode,
        cache_ttl=cache_ttl,
    )

render_change_report

render_change_report(
    report: ChangeReport,
    *,
    formats: Sequence[str] = ("html", "markdown", "json"),
    options: RenderOptions | None = None,
) -> RenderedReportBundle

Render a ChangeReport offline into any requested subset of 'html', 'json', and 'markdown'.

Source code in src/arodonata/client.py
@traced
def render_change_report(
    self,
    report: ChangeReport,
    *,
    formats: Sequence[str] = ("html", "markdown", "json"),
    options: RenderOptions | None = None,
) -> RenderedReportBundle:
    """Render a `ChangeReport` offline into any requested subset of `'html'`, `'json'`, and `'markdown'`."""
    return _render_change_report(
        report,
        formats=formats,
        options=options,
    )

search_objects async

search_objects(
    query: str,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    max_depth: int = 2,
    exact_match: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> SearchResult

Search cached objects across comma-separated terms and resolve parent groups up to max_depth.

Source code in src/arodonata/client.py
@traced
async def search_objects(
    self,
    query: str,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    max_depth: int = 2,
    exact_match: bool = False,
    cache_mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> SearchResult:
    """Search cached objects across comma-separated terms and resolve parent groups up to `max_depth`."""
    if not self._db.is_initialized:
        await self._db.initialize()
    return await self._search_service.search_objects(
        query,
        mgmt_name=mgmt_name,
        domains=domains,
        types=types,
        include_global=include_global,
        max_depth=max_depth,
        exact_match=exact_match,
        cache_mode=cache_mode,
        cache_ttl=cache_ttl,
    )

set_object async

set_object(
    mgmt_name: str,
    obj_type: str,
    payload: dict[str, Any],
    *,
    domain: str | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
) -> ApiCallResult

Update a Check Point object (set-<obj_type>) using active WriteSession context, explicit sid, or an automatic isolated write_session (C8).

Source code in src/arodonata/client.py
@traced
async def set_object(
    self,
    mgmt_name: str,
    obj_type: str,
    payload: dict[str, Any],
    *,
    domain: str | None = None,
    sid: str | None = None,
    server_ip: str | None = None,
) -> ApiCallResult:
    """Update a Check Point object (`set-<obj_type>`) using active `WriteSession` context, explicit `sid`, or an automatic isolated `write_session` (`C8`)."""
    clean_type = obj_type.strip().lower().removeprefix("set-")
    span_attrs(mgmt_name=mgmt_name, domain=domain, object_type=clean_type)
    dom_key = normalize_domain_key(domain) if domain is not None else None
    if sid is None and get_write_session(mgmt_name=mgmt_name, domain_name=dom_key) is None:
        async with self.write_session(mgmt_name, domain) as ws:
            return await self.api_call(
                mgmt_name,
                f"set-{clean_type}",
                payload,
                domain=domain,
                sid=ws.sid,
                server_ip=server_ip or ws.server_ip,
            )
    return await self.api_call(
        mgmt_name,
        f"set-{clean_type}",
        payload,
        domain=domain,
        sid=sid,
        server_ip=server_ip,
    )

start_object_cache_warmup

start_object_cache_warmup(
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    include_services: bool = False,
    mode: CacheMode = "smart",
) -> asyncio.Task[RefreshOutcome]

Explicitly schedule background object (and optional service) cache warm-up.

Source code in src/arodonata/client.py
def start_object_cache_warmup(
    self,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    include_services: bool = False,
    mode: CacheMode = "smart",
) -> asyncio.Task[RefreshOutcome]:
    """Explicitly schedule background object (and optional service) cache warm-up."""

    async def _run_warmup() -> RefreshOutcome:
        if not self._db.is_initialized:
            await self._db.initialize()
        obj_outcome = await self.refresh_objects(
            mgmt_name=mgmt_name,
            domains=domains,
            types=types,
            include_global=include_global,
            mode=mode,
        )
        if include_services:
            await self.refresh_services(
                mgmt_name=mgmt_name,
                domains=domains,
                include_global=include_global,
                mode=mode,
            )
        return obj_outcome

    task = asyncio.create_task(_run_warmup())
    self._warmup_tasks.add(task)
    task.add_done_callback(self._warmup_tasks.discard)
    return task

stream_publish_staged_session async

stream_publish_staged_session(
    mgmt_name: str,
    domain: str | None = None,
    *,
    session_uid: str,
    sid: str | None = None,
    server_ip: str | None = None,
    timeout: float | None = None,
    auth: AuthContext | None = None,
    force_disconnect: bool = False,
) -> AsyncIterator[CPCRUDEvent]

Adopt session_uid, publish it while streaming CPCRUDEvent items, log out, and invalidate the domain cache.

Source code in src/arodonata/client.py
async def stream_publish_staged_session(
    self,
    mgmt_name: str,
    domain: str | None = None,
    *,
    session_uid: str,
    sid: str | None = None,
    server_ip: str | None = None,
    timeout: float | None = None,
    auth: AuthContext | None = None,
    force_disconnect: bool = False,
) -> AsyncIterator[CPCRUDEvent]:
    """Adopt `session_uid`, publish it while streaming `CPCRUDEvent` items, log out, and invalidate the domain cache."""
    async for ev in self._cpcrud.stream_publish_staged_session(
        mgmt_name,
        domain,
        session_uid=session_uid,
        sid=sid,
        server_ip=server_ip,
        timeout=timeout,
        auth=auth,
        force_disconnect=force_disconnect,
    ):
        yield ev

stream_refresh_assets async

stream_refresh_assets(
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> AsyncIterator[SyncProgressEvent]

Stream SyncProgressEvent items while synchronizing gateway, cluster, VSX, and server assets.

Source code in src/arodonata/client.py
async def stream_refresh_assets(
    self,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> AsyncIterator[SyncProgressEvent]:
    """Stream `SyncProgressEvent` items while synchronizing gateway, cluster, VSX, and server assets."""
    if not self._db.is_initialized:
        await self._db.initialize()
    async for ev in self._asset_service.stream_refresh_assets(
        mgmt_name=mgmt_name,
        domains=domains,
        include_global=include_global,
        mode=mode,
        cache_ttl=cache_ttl,
    ):
        yield ev

stream_refresh_objects async

stream_refresh_objects(
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> AsyncIterator[SyncProgressEvent]

Stream SyncProgressEvent items while synchronizing network objects.

Source code in src/arodonata/client.py
async def stream_refresh_objects(
    self,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> AsyncIterator[SyncProgressEvent]:
    """Stream `SyncProgressEvent` items while synchronizing network objects."""
    if not self._db.is_initialized:
        await self._db.initialize()
    async for ev in self._object_service.stream_refresh_objects(
        mgmt_name=mgmt_name,
        domains=domains,
        types=types,
        include_global=include_global,
        mode=mode,
        cache_ttl=cache_ttl,
    ):
        yield ev

stream_refresh_rulebases async

stream_refresh_rulebases(
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> AsyncIterator[SyncProgressEvent]

Stream SyncProgressEvent items while synchronizing policy rulebase snapshots.

Source code in src/arodonata/client.py
async def stream_refresh_rulebases(
    self,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> AsyncIterator[SyncProgressEvent]:
    """Stream `SyncProgressEvent` items while synchronizing policy rulebase snapshots."""
    if not self._db.is_initialized:
        await self._db.initialize()
    async for ev in self._rulebase_refresh.stream_refresh_rulebases(
        mgmt_name=mgmt_name,
        domains=domains,
        include_global=include_global,
        mode=mode,
        cache_ttl=cache_ttl,
    ):
        yield ev

stream_refresh_services async

stream_refresh_services(
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> AsyncIterator[SyncProgressEvent]

Stream SyncProgressEvent items while synchronizing service objects.

Source code in src/arodonata/client.py
async def stream_refresh_services(
    self,
    *,
    mgmt_name: str | None = None,
    domains: Sequence[str] | str | None = None,
    types: Sequence[str] | None = None,
    include_global: bool = False,
    mode: CacheMode = "smart",
    cache_ttl: float | None = None,
) -> AsyncIterator[SyncProgressEvent]:
    """Stream `SyncProgressEvent` items while synchronizing service objects."""
    if not self._db.is_initialized:
        await self._db.initialize()
    async for ev in self._service_cache.stream_refresh_services(
        mgmt_name=mgmt_name,
        domains=domains,
        types=types,
        include_global=include_global,
        mode=mode,
        cache_ttl=cache_ttl,
    ):
        yield ev

user_context

user_context(
    user: UserContext | None = None,
    *,
    user_id: str | None = None,
    username: str | None = None,
    session_name: str | None = None,
    session_description: str | None = None,
    client_id: str | None = None,
    extra: dict[str, Any] | None = None,
) -> Iterator[UserContext]

Bind operator attribution metadata (UserContext) in contextvars.

Source code in src/arodonata/client.py
@contextmanager
def user_context(
    self,
    user: UserContext | None = None,
    *,
    user_id: str | None = None,
    username: str | None = None,
    session_name: str | None = None,
    session_description: str | None = None,
    client_id: str | None = None,
    extra: dict[str, Any] | None = None,
) -> Iterator[UserContext]:
    """Bind operator attribution metadata (`UserContext`) in `contextvars`."""
    with _ctx_user_context(
        user,
        user_id=user_id,
        username=username,
        session_name=session_name,
        session_description=session_description,
        client_id=client_id,
        extra=extra,
    ) as resolved:
        yield resolved

write_session async

write_session(
    mgmt_name: str,
    domain: str | None = None,
    *,
    auth: AuthContext | None = None,
    user: UserContext | None = None,
    session_name: str | None = None,
    session_description: str | None = None,
    publish_on_exit: bool = True,
) -> AsyncIterator[WriteSession]

Open a context-bound isolated WriteSession with SessionOverlay read-your-own-writes isolation.

Source code in src/arodonata/client.py
@asynccontextmanager
async def write_session(
    self,
    mgmt_name: str,
    domain: str | None = None,
    *,
    auth: AuthContext | None = None,
    user: UserContext | None = None,
    session_name: str | None = None,
    session_description: str | None = None,
    publish_on_exit: bool = True,
) -> AsyncIterator[WriteSession]:
    """Open a context-bound isolated `WriteSession` with `SessionOverlay` read-your-own-writes isolation."""
    if not self._db.is_initialized:
        await self._db.initialize()

    if self._amgmt is not None:
        async with self._amgmt.write_session(
            mgmt_name,
            domain,
            auth=auth,
            user=user,
            session_name=session_name,
            session_description=session_description,
            publish_on_exit=publish_on_exit,
        ) as ws:
            yield ws
        return

    # Fallback for custom ApiPort implementations injected in tests
    dom_key = normalize_domain_key(domain)
    resolved_user = user or get_user_context()
    eff_name = session_name or (resolved_user.session_name if resolved_user else None)
    eff_desc = session_description or (resolved_user.session_description if resolved_user else None)

    sid, server_ip = await self._api.create_dedicated_session(
        mgmt_name,
        domain,
        auth=auth,
        session_name=eff_name,
        session_description=eff_desc,
    )
    ws = WriteSession(
        mgmt_name=mgmt_name,
        domain_name=dom_key,
        sid=sid,
        server_ip=server_ip,
        session_name=eff_name,
        auth=auth,
        user=resolved_user,
        overlay=SessionOverlay(mgmt_name, dom_key),
    )
    try:
        with write_session_context(ws):
            try:
                yield ws
            except BaseException:
                if not ws.discarded and not ws.published:
                    with suppress(BaseException):
                        await asyncio.shield(
                            self.api_call(
                                mgmt_name,
                                "discard",
                                {},
                                domain=domain,
                                sid=ws.sid,
                                server_ip=ws.server_ip,
                            )
                        )
                    ws.discarded = True
                    ws.overlay.clear()
                raise

            if publish_on_exit and not ws.published and not ws.discarded:
                pub_res = await self.api_call(
                    mgmt_name,
                    "publish",
                    {},
                    domain=domain,
                    sid=ws.sid,
                    server_ip=ws.server_ip,
                    wait_for_task=True,
                )
                if not pub_res.success:
                    with suppress(BaseException):
                        await asyncio.shield(
                            self.api_call(
                                mgmt_name,
                                "discard",
                                {},
                                domain=domain,
                                sid=ws.sid,
                                server_ip=ws.server_ip,
                            )
                        )
                    ws.discarded = True
                    ws.overlay.clear()
                    raise SessionError(
                        f"Failed to publish WriteSession on {mgmt_name!r} ({dom_key!r}): "
                        f"{pub_res.error_message or 'publish failed'}"
                    )
                ws.published = True
                ws.overlay.clear()
                self.invalidate_domain(mgmt_name, domain)
    finally:
        ws.overlay.clear()
        with suppress(BaseException):
            await asyncio.shield(self.logout_sid(mgmt_name, ws.sid, server_ip=ws.server_ip))