Skip to content

Commit f45612a

Browse files
committed
Merge revised reporting protocols into main
Add bounded summary discovery, streamed checks and histories, scoped enterprise ownership, portable source identities, and asynchronous preview contracts. Include the reporting reference generator and contract validation tests. Keep main's newer catalog implementation and its current design documents. Tighten test annotations for main's strict mypy checks. Validation: 392 reporting tests pass; repository Ruff lint/format and mypy for source and tests pass. Full suite: 3518 passed, 148 skipped, and two sibling checkout drift failures reproduced on unchanged main (C++ attach-ticket test parity and the checked-in Rust schema).
2 parents 6640508 + a463227 commit f45612a

24 files changed

Lines changed: 5563 additions & 0 deletions

‎tests/test_reporting_contracts.py‎

Lines changed: 312 additions & 0 deletions
Large diffs are not rendered by default.

‎tests/test_reporting_enterprise.py‎

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# Copyright 2025, 2026 Query Farm LLC - https://query.farm
2+
3+
"""Enterprise ownership selection, canonical identity and advertised scope contracts."""
4+
5+
from __future__ import annotations
6+
7+
from collections.abc import Hashable
8+
from dataclasses import replace
9+
from typing import Any, cast
10+
11+
import pytest
12+
from vgi_rpc import ArrowSerializableDataclass
13+
from vgi_rpc.errors import Code, error_code_of
14+
from vgi_rpc.rpc import rpc_methods
15+
16+
from vgi.reporting import PROTOCOLS
17+
from vgi.reporting.common import OwnerKey, OwnerRef, Ownership, OwnershipCapabilities, PrincipalRef
18+
from vgi.reporting.discovery import (
19+
MembershipUnavailableError,
20+
OwnerDiscoveryCapabilities,
21+
matches_ownership_scope,
22+
validate_list_request,
23+
validate_owner_key,
24+
validate_ownership_assignment,
25+
validate_ownership_capabilities,
26+
validate_search,
27+
)
28+
from vgi.reporting.resource_identity import Identifier, IdentityCapabilities, ResourceContext, validate_identity_request
29+
30+
31+
def _policy(*, membership: bool = True) -> OwnershipCapabilities:
32+
"""A worker supports teams and organizations independently of directory discovery."""
33+
return OwnershipCapabilities(
34+
owner_kinds=["principal", "group", "organization", "workspace"],
35+
parent_owner_kinds=["group", "organization", "workspace"],
36+
scopes=["all", "self", "member", "self_or_member"] if membership else ["all", "self"],
37+
membership_owner_kinds=["group", "organization"] if membership else [],
38+
)
39+
40+
41+
def _ownership(kind: str, identifier: str) -> Ownership:
42+
"""Team and personal resources share a durable organizational management parent."""
43+
return Ownership(
44+
owner_ref=OwnerRef(kind=kind, id=identifier, display_name="Display only"),
45+
parent_owner_ref=OwnerRef(kind="organization", id="tenant-a/org", display_name="Organization"),
46+
)
47+
48+
49+
def test_member_scope_uses_only_direct_owner_and_never_enumerates_groups() -> None:
50+
"""Effective membership checks one canonical owner, without expanding parent ancestry."""
51+
checked: list[OwnerKey] = []
52+
53+
def membership(key: OwnerKey) -> bool:
54+
checked.append(key)
55+
return key.id == "tenant-a/groups/finance"
56+
57+
args: dict[str, Any] = dict(
58+
principal_id="tenant-a/users/alice", scope="member", capabilities=_policy(), membership_lookup=membership
59+
)
60+
assert matches_ownership_scope(_ownership("group", "tenant-a/groups/finance"), **args)
61+
assert not matches_ownership_scope(_ownership("group", "tenant-a/other-issuer/groups/finance"), **args)
62+
assert not matches_ownership_scope(_ownership("principal", "tenant-a/users/bob"), **args)
63+
assert not matches_ownership_scope(_ownership("workspace", "tenant-a/workspace"), **args)
64+
assert checked == [
65+
OwnerKey(kind="group", id="tenant-a/groups/finance"),
66+
OwnerKey(kind="group", id="tenant-a/other-issuer/groups/finance"),
67+
]
68+
69+
70+
def test_self_and_union_scopes_use_authenticated_user_or_service_identity() -> None:
71+
"""Self matching needs no membership lookup and never treats group labels as caller IDs."""
72+
for kind in ("user", "service"):
73+
actor = PrincipalRef(id=f"tenant-a/{kind}/actor", kind=cast(Any, kind), display_name="Actor", email=None)
74+
own = _ownership("principal", actor.id)
75+
assert matches_ownership_scope(own, principal_id=actor.id, scope="self", capabilities=_policy())
76+
assert matches_ownership_scope(own, principal_id=actor.id, scope="self_or_member", capabilities=_policy())
77+
assert not matches_ownership_scope(own, principal_id=actor.id, scope="member", capabilities=_policy())
78+
assert not matches_ownership_scope(
79+
_ownership("group", actor.id), principal_id=actor.id, scope="self", capabilities=_policy()
80+
)
81+
82+
83+
@pytest.mark.parametrize("answer", [None, "yes", 1])
84+
def test_indeterminate_membership_cannot_become_allowed_or_empty_success(answer: Any) -> None:
85+
"""Only authoritative booleans are membership answers; incomplete decisions remain unavailable."""
86+
with pytest.raises(MembershipUnavailableError):
87+
matches_ownership_scope(
88+
_ownership("group", "tenant-a/groups/finance"),
89+
principal_id="tenant-a/users/alice",
90+
scope="member",
91+
capabilities=_policy(),
92+
membership_lookup=lambda key: answer,
93+
)
94+
95+
96+
def test_membership_revocation_and_provider_failure_are_not_cached_as_permission() -> None:
97+
"""Each invocation rechecks effective membership; outage propagates separately from a negative decision."""
98+
current: bool | None = True
99+
100+
def membership(key: OwnerKey) -> bool | None:
101+
return current
102+
103+
own = _ownership("group", "tenant-a/groups/finance")
104+
args: dict[str, Any] = dict(
105+
principal_id="tenant-a/users/alice", scope="member", capabilities=_policy(), membership_lookup=membership
106+
)
107+
assert matches_ownership_scope(own, **args)
108+
current = False
109+
assert not matches_ownership_scope(own, **args)
110+
current = None
111+
with pytest.raises(MembershipUnavailableError) as error:
112+
matches_ownership_scope(own, **args)
113+
assert error_code_of(error.value) is Code.UNAVAILABLE
114+
115+
def failed_provider(key: OwnerKey) -> bool:
116+
raise TimeoutError("Identity authority did not answer")
117+
118+
with pytest.raises(TimeoutError):
119+
matches_ownership_scope(own, **(args | {"membership_lookup": failed_provider}))
120+
121+
122+
def test_exact_group_resolution_and_filtering_work_without_membership_or_search() -> None:
123+
"""A restricted provider can resolve supplied group IDs and filter their resources."""
124+
policy = _policy(membership=False)
125+
discovery = OwnerDiscoveryCapabilities(policy=policy, identifier_schemes=["group_id"])
126+
context = ResourceContext(protocol="vgi.objects.v1", resource_kind="object", operation="create")
127+
validate_identity_request(
128+
context,
129+
purpose="ownership",
130+
identifier=Identifier(scheme="group_id", value="finance"),
131+
capabilities=IdentityCapabilities(ownership=discovery, execution=None),
132+
supports_folders=True,
133+
)
134+
validate_list_request(
135+
limit=10,
136+
order_by="id",
137+
supported_orders=("id",),
138+
owner=OwnerKey(kind="group", id="tenant-a/finance"),
139+
ownership_capabilities=policy,
140+
)
141+
with pytest.raises(NotImplementedError):
142+
validate_search("finance", 10, discovery)
143+
for scope in ("member", "self_or_member"):
144+
with pytest.raises(NotImplementedError):
145+
validate_list_request(
146+
limit=10,
147+
order_by="id",
148+
supported_orders=("id",),
149+
ownership_scope=cast(Any, scope),
150+
ownership_capabilities=policy,
151+
)
152+
153+
154+
def test_membership_filter_support_does_not_require_search_support() -> None:
155+
"""Group resource views can work even when the directory forbids user-facing searches."""
156+
policy = _policy()
157+
discovery = OwnerDiscoveryCapabilities(policy=policy, identifier_schemes=["group_id"])
158+
validate_list_request(
159+
limit=10,
160+
order_by="id",
161+
supported_orders=("id",),
162+
ownership_scope="member",
163+
ownership_capabilities=policy,
164+
parent_owner=OwnerKey(kind="organization", id="tenant-a/org"),
165+
)
166+
with pytest.raises(NotImplementedError):
167+
validate_search("finance", 10, discovery)
168+
169+
170+
@pytest.mark.parametrize("scope", ["self", "member", "self_or_member"])
171+
def test_explicit_owner_conflicts_with_non_all_scope(scope: Any) -> None:
172+
"""Clients must choose exact ownership or relative caller scope, without ambiguous precedence."""
173+
with pytest.raises(ValueError, match="mutually exclusive"):
174+
validate_list_request(
175+
limit=None,
176+
order_by="id",
177+
supported_orders=("id",),
178+
ownership_scope=scope,
179+
owner=OwnerKey(kind="group", id="tenant-a/finance"),
180+
ownership_capabilities=_policy(),
181+
)
182+
183+
184+
@pytest.mark.parametrize(
185+
"change",
186+
[
187+
{"scopes": ["all"]},
188+
{"scopes": ["all", "self", "member"]},
189+
{"scopes": ["all", "self"]},
190+
{"scopes": ["all", "self", "everyone"]},
191+
{"membership_owner_kinds": []},
192+
{"membership_owner_kinds": ["principal"]},
193+
{"membership_owner_kinds": ["vendor.absent"]},
194+
{"parent_owner_kinds": ["principal"]},
195+
{"parent_owner_kinds": []},
196+
{"owner_kinds": ["team"]},
197+
{"owner_kinds": ["group", "group"]},
198+
],
199+
)
200+
def test_contradictory_capabilities_are_rejected(change: dict[str, Any]) -> None:
201+
"""Workers cannot advertise incomplete membership scopes or ambiguous owner vocabulary."""
202+
with pytest.raises(ValueError):
203+
validate_ownership_capabilities(replace(_policy(), **change))
204+
205+
206+
def test_canonical_owner_vocabulary_and_durable_parent() -> None:
207+
"""Teams use group keys; personal resources need a supported durable management parent."""
208+
validate_owner_key(OwnerKey(kind="vendor.cost_center", id="tenant-a/center-1"))
209+
for key in (
210+
OwnerKey(kind="team", id="finance"),
211+
OwnerKey(kind="group", id=" "),
212+
OwnerKey(kind="vendor.", id="x"),
213+
OwnerKey(kind="vendor..name", id="x"),
214+
):
215+
with pytest.raises(ValueError):
216+
validate_owner_key(key)
217+
own = _ownership("principal", "tenant-a/users/alice")
218+
validate_ownership_assignment(own, _policy())
219+
with pytest.raises(ValueError, match="durable parent"):
220+
validate_ownership_assignment(replace(own, parent_owner_ref=None), _policy())
221+
with pytest.raises(ValueError, match="distinct"):
222+
group = _ownership("group", "tenant-a/finance")
223+
validate_ownership_assignment(replace(group, parent_owner_ref=group.owner_ref), _policy())
224+
validate_ownership_assignment(
225+
Ownership(
226+
owner_ref=OwnerRef(kind="organization", id="tenant-a/org", display_name="Org"), parent_owner_ref=None
227+
),
228+
_policy(),
229+
)
230+
231+
232+
def test_every_resource_and_folder_collection_advertises_consistent_scopes() -> None:
233+
"""Normal service info advertises team filtering even without the optional identity companion."""
234+
count = 0
235+
for protocol in PROTOCOLS:
236+
methods = rpc_methods(cast(Hashable, protocol))
237+
for name, method in methods.items():
238+
if name.startswith(("list_", "scan_")) and "owner" in method.param_types:
239+
count += 1
240+
assert method.param_defaults["ownership_scope"] == "all"
241+
assert "owned_by_me" not in method.param_types
242+
assert "parent_owner" in method.param_types
243+
info = next(m for n, m in methods.items() if n.startswith("get_") and n.endswith("_info"))
244+
result_type = info.result_type
245+
assert isinstance(result_type, type) and issubclass(result_type, ArrowSerializableDataclass)
246+
assert result_type.ARROW_SCHEMA.field("ownership").type.num_fields == 4
247+
assert count == 16

0 commit comments

Comments
 (0)