"""Self-service (non-admin) server visibility for the cloud surfaces. One shared answer to "which servers may this person see?" — used by the cloud-servers list/detail, the efficiency / cost-optimization surface, and their MCP mirrors, so a machine never shows on one page and 404s on the next. Historically self-scope was only ``cmdb_servers.owner_person_id``, but server-level owner is sparsely populated (most fleets attribute ownership at the cloud-subscription level), so nearly every employee saw empty pages. The expanded contract (2026-07-22, user-approved): a person sees servers they (or their transitive reports) own at server level, PLUS every server attributed to a cloud subscription they (or their reports) own. Attribution uses the same links as ``resolve_inherited_server_owners``: * the provider account stamp every sync driver writes onto ``raw_data`` (``SCOPE_ACCOUNT_RAW_KEYS``: AWS ``_aws_account_id``, GCP ``_project_id``, Azure ``_subscription_id``, AliCloud ``__sub_account_id``) → scope ``external_id``; * any cloud — a ``cmdb_cost_facts`` line (``resource_id == instance_id`` within the integration) carrying the scope's ``scope_id``. **2026-08-18 — the stamp link used to be GCP-only here**, so for two clouds the contract above was not delivered at all: AWS cost lines are aggregated per account (``resource_id`` = ``aws-account::``, ``resource_type='aws-aggregated'``) and Azure's ``resource_id`` is the ARM path, so neither reaches the billing link either — a subscription owner could not see the machines in their own subscription. 5136 AWS + 11 Azure servers were affected. Widening to all four stamps stays inside the performance contract below: it is still a JSON equality on the servers table (four extracts instead of one), with no fact-table access added to the per-request plan. Performance contract (2026-07-23 prod 8176 incident): the cost-facts link is materialized ONCE here at resolve time — a single bounded query driven by the ``ix_facts_scope_resource`` index (migration 427) — and the request-time SQL clause is a plain ``cmdb_servers.id IN (...)``. The first rollout embedded a correlated EXISTS against the (multi- million-row) fact table into every page query; TiDB decorrelated it into a full materialization and killed the query for exceeding the instance memory limit. Never re-introduce cost-fact scans into the per-request plan. Read-only primitive: nothing here grants any write authority beyond what the caller already enforces. """ from __future__ import annotations import logging from dataclasses import dataclass, field from typing import Any from sqlalchemy import and_, or_, select from sqlalchemy.ext.asyncio import AsyncSession from app.domains.cmdb.models import ( SCOPE_ACCOUNT_RAW_KEYS, CmdbCloudScope, CmdbCostFact, CmdbServer, ) logger = logging.getLogger(__name__) # Defensive ceiling on the materialized scope→server set. A person's # owned subscriptions attribute hundreds, maybe a few thousand machines; # a set at the cap almost certainly means broken attribution data, and a # silently-truncated page beats an unbounded IN() list. Logged when hit. _SCOPE_SERVER_CAP = 20_000 @dataclass class OwnerVisibility: """Resolved visibility set for a self-service (non-admin) reader.""" person_ids: list[str] # Live cloud subscriptions owned by ``person_ids`` (kept for callers # that reason about scopes, e.g. debugging / future filters). scope_ids: list[str] = field(default_factory=list) # (integration_id, external_id) of owned scopes — matched against the # account stamp the server's driver wrote onto ``raw_data`` # (``SCOPE_ACCOUNT_RAW_KEYS``; one key per cloud, so a server matches # on at most one of them). account_pairs: list[tuple[str, str]] = field(default_factory=list) # Server ids attributed to the owned scopes via cost facts, # materialized at resolve time (see the performance contract above). scope_server_ids: list[str] = field(default_factory=list) async def resolve_owner_visibility( db: AsyncSession, person_ids: list[str], ) -> OwnerVisibility: """Build the :class:`OwnerVisibility` for a set of persons (self + subordinates): their ids, the live cloud subscriptions they own, and the materialized set of servers those subscriptions attribute.""" rows = ( await db.execute( select( CmdbCloudScope.id, CmdbCloudScope.integration_id, CmdbCloudScope.external_id, ).where( CmdbCloudScope.owner_person_id.in_(person_ids), CmdbCloudScope.deleted_at.is_(None), ) ) ).all() scope_ids = [str(sid) for sid, _, _ in rows] scope_server_ids: list[str] = [] if scope_ids: # Drive from the fact table's (scope_id, resource_id) index, join # up to servers on the unique (integration_id, instance_id) key. # DISTINCT keeps the result at fleet-size, not line-item-size. sid_rows = await db.execute( select(CmdbServer.id) .join( CmdbCostFact, and_( CmdbCostFact.integration_id == CmdbServer.integration_id, CmdbCostFact.resource_id == CmdbServer.instance_id, ), ) .where( CmdbCostFact.scope_id.in_(scope_ids), CmdbServer.deleted_at.is_(None), ) .distinct() .limit(_SCOPE_SERVER_CAP) ) scope_server_ids = [str(x) for x in sid_rows.scalars()] if len(scope_server_ids) >= _SCOPE_SERVER_CAP: logger.warning( "owner visibility hit the %d scope-server cap for persons %s " "— attribution data likely broken; page is truncated", _SCOPE_SERVER_CAP, person_ids[:3], ) return OwnerVisibility( person_ids=list(person_ids), scope_ids=scope_ids, account_pairs=[ (str(integ), str(ext)) for _, integ, ext in rows if ext ], scope_server_ids=scope_server_ids, ) def visibility_clause(vis: OwnerVisibility) -> Any: """SQL predicate: the outer ``CmdbServer`` is visible to this reader. Cheap by construction — owner IN, materialized id IN, and a JSON-field equality per account-stamp key on the servers table. No fact-table access.""" clauses: list[Any] = [CmdbServer.owner_person_id.in_(vis.person_ids)] if vis.scope_server_ids: clauses.append(CmdbServer.id.in_(vis.scope_server_ids)) for raw_key in SCOPE_ACCOUNT_RAW_KEYS: if not vis.account_pairs: break stamp = CmdbServer.raw_data[raw_key].as_string() clauses.append( or_(*[ and_(CmdbServer.integration_id == integ, stamp == ext) for integ, ext in vis.account_pairs ]) ) return or_(*clauses) async def is_server_visible( db: AsyncSession, server_id: str, vis: OwnerVisibility, ) -> bool: """Whether one (live) server falls inside this reader's visibility.""" stmt = select(CmdbServer.id).where( CmdbServer.id == server_id, CmdbServer.deleted_at.is_(None), visibility_clause(vis), ) return (await db.execute(stmt)).scalar_one_or_none() is not None