| name | endor-route-estate-queries |
| description | Use when routing estate-scale data pulls between Query graph joins and facade
list/count/shard patterns after bounded topology discovery—choosing how to fetch
counts, aggregates, or validated joins across many projects. Not for single-project
RCA; hand off when full row export is needed without a validated Query join.
|
Route estate queries (Query vs facade)
Default path: classify the ask → pick grain (project vs namespace) → discover if needed → validate on a sample → scale.
Normative parity: query-vs-list-semantics contract. Guide: docs/guides/query-recipes.md.
Scope
| In scope | Out of scope |
|---|
| Dashboard counts across many projects | Single-project finding RCA |
Query graph joins vs count() / list_groups | Unvalidated custom joins at full tenant |
Topology discovery (bounded Project.list) | endor-estate pull unless user asks |
| Namespace-scoped Query (non-Project roots) | Policy validation |
| Sample validation before estate-wide Query | |
Handoffs: endor-retrieve-scan-results (one repo rows) · endor-project-retrieval-bundle (single-project bundle) · endor-namespace-relationship-map (consumer graph).
Step 0 — Classify output and grain
Per-project grain (estate dashboard)
| User wants | OutputShape | Primary path |
|---|
| PV / finding category counts per project | COUNT_BY_PROJECT / FINDING_CATEGORY_COUNTS | Query.Project.* after validation |
| DM count per importer project | (dashboard) | client.Query.Project.count_dm after recipe="dm" validation |
| Online estate dashboard tiles (no pull) | — | fetch_online_dashboard_counts → ir/online_dashboard_counts.json |
| Finding rows for one scan | FINDING_ROWS | Finding.list_by_project |
| Package usage by version | DM_VERSION_CARDINALITY | DependencyMetadata.list_groups |
from endorlabs.query import OutputShape, discover_topology, recommend
topo = discover_topology(client, "<tenant>", traverse=True, max_pages=...)
plan = recommend(OutputShape.COUNT_BY_PROJECT, topology=topo)
shards = topo.project_shards()
Namespace grain (no Project root)
| User wants | Primary path |
|---|
| Count/filter at one namespace (e.g. agent hook events) | client.Query.at_namespace(QuerySpec.root("<Kind>").count(...), namespace=…) or facade count() |
| Tenant-wide finding total (no per-project breakdown) | Probe Query.at_namespace with Finding root; compare to Finding.count |
| New vs resolved over time | FindingLog.list_groups (probe Query group_by_time) |
| OSS CVE/coordinate lookup | QueryVulnerability / QueryMalware |
from endorlabs.query import QuerySpec
spec = QuerySpec.root("AgentHookEvent").list_parameters(count=True)
client.Query.at_namespace(spec, namespace="<leaf>", parse=..., merge=...)
Do not force Query.Project.discover when the ask has no project grain.
Step 1 — Discover topology (per-project grain only)
from endorlabs.query import discover_topology
topo = discover_topology(
client,
"<tenant>",
traverse=True,
max_pages=...,
)
Resolve Project rows first; pass resource objects into Query.Project recipes.
Step 2 — Correctness gate (before scale)
from endorlabs.query import validate_sample
sample = topo.projects[:10]
result = validate_sample(client, sample, recipe="pv", sample_size=5)
assert result.matched, result.to_dict()
counts = client.Query.Project.count_pv(topo.projects)
dm_counts = client.Query.Project.count_dm(topo.projects)
Canonical MQL: endorlabs.filters.
Step 3 — Execute
| Archetype | Shard key | Query? |
|---|
single_repo | project | Optional; recommend() may prefer facade_count |
monorepo_hub | leaf namespace + pagination | Yes for validated count joins |
managed_platform / estate_sprawl | leaf namespace batches | Yes if sample validated |
Row materialization: topo.project_shards() → tools/list_sharding. Online-only dashboard: fetch_online_dashboard_counts.
Anti-patterns
- Treat
Query.Project as the only Query API — use execute / at_namespace for other root kinds
- POST
Query at tenant root when projects live in child namespaces
- Use
Query.Project.count_dm when the ask needs DM version buckets (list_groups semantics differ)
- Skip sample validation because Query was faster on another customer
traverse=True on findings after Project is already resolved
Related skills