Complete Phase 1: policy kernel, REST service, and local smoke tooling
Implements QONTO-WP-0002 (policy-gated Qonto REST service with audit logging, rate limiting, and credential handling) and the ADHOC-2026-07-21 follow-up (fixture-backed local smoke mode, repo classification metadata). Marks QONTO-WP-0001/0002 and the ad-hoc workplan finished, and regenerates WORK-RECORDS.md and the ADHOC workplan's state_hub_workstream_id via fix-consistency. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
eef408bb19
commit
ca12843013
33 changed files with 2533 additions and 30 deletions
87
tests/test_policy.py
Normal file
87
tests/test_policy.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
from pathlib import Path
|
||||
|
||||
from qonto_assistant.contracts import ActorClaims, CapabilityRequest
|
||||
from qonto_assistant.policy import PolicyEngine
|
||||
|
||||
POLICY_FILE = Path(__file__).resolve().parents[1] / "src" / "qonto_assistant" / "policy" / "qonto-v1.yaml"
|
||||
|
||||
|
||||
def _policy(*, enforce_scope: bool = False) -> PolicyEngine:
|
||||
return PolicyEngine.from_file(
|
||||
POLICY_FILE,
|
||||
required_scope="finance.qonto.read",
|
||||
enforce_scope=enforce_scope,
|
||||
)
|
||||
|
||||
|
||||
def _request(capability_id: str, **request_args: object) -> CapabilityRequest:
|
||||
claims = ActorClaims(
|
||||
actor_id="agent-1",
|
||||
tenant_id="binky",
|
||||
lane="green",
|
||||
scopes=frozenset({"finance.qonto.read"}),
|
||||
)
|
||||
return CapabilityRequest(
|
||||
capability_id=capability_id,
|
||||
tenant_id="binky",
|
||||
actor_claims=claims,
|
||||
resource_scope="test",
|
||||
request_args=dict(request_args),
|
||||
protocol="rest",
|
||||
)
|
||||
|
||||
|
||||
def test_policy_allows_known_read_capability() -> None:
|
||||
decision = _policy().decide(_request("org_summary"))
|
||||
assert decision.allowed is True
|
||||
assert decision.reason == "allow"
|
||||
|
||||
|
||||
def test_policy_denies_cross_tenant_requests() -> None:
|
||||
claims = ActorClaims(actor_id="agent-1", tenant_id="other", lane="green")
|
||||
request = CapabilityRequest(
|
||||
capability_id="org_summary",
|
||||
tenant_id="binky",
|
||||
actor_claims=claims,
|
||||
resource_scope="accounts",
|
||||
request_args={},
|
||||
protocol="rest",
|
||||
)
|
||||
decision = _policy().decide(request)
|
||||
assert decision.allowed is False
|
||||
assert decision.reason == "tenant_scope"
|
||||
|
||||
|
||||
def test_policy_denies_excessive_page_size() -> None:
|
||||
decision = _policy().decide(_request("list_transactions", page=1, page_size=101, window_days=31))
|
||||
assert decision.allowed is False
|
||||
assert decision.reason == "arg_constraint"
|
||||
|
||||
|
||||
def test_policy_denies_volume_cost_shaped_requests() -> None:
|
||||
decision = _policy().decide(
|
||||
_request("list_transactions", page=1, page_size=50, window_days=31, operation_type="card_operation")
|
||||
)
|
||||
assert decision.allowed is False
|
||||
assert decision.reason == "volume_cost"
|
||||
|
||||
|
||||
def test_policy_denies_credential_exfiltration_flags() -> None:
|
||||
decision = _policy().decide(_request("list_transactions", page=1, page_size=50, window_days=31, include_full_iban=True))
|
||||
assert decision.allowed is False
|
||||
assert decision.reason == "credential_exfil"
|
||||
|
||||
|
||||
def test_policy_enforces_scope_when_enabled() -> None:
|
||||
claims = ActorClaims(actor_id="agent-1", tenant_id="binky", lane="green", scopes=frozenset())
|
||||
request = CapabilityRequest(
|
||||
capability_id="org_summary",
|
||||
tenant_id="binky",
|
||||
actor_claims=claims,
|
||||
resource_scope="accounts",
|
||||
request_args={},
|
||||
protocol="rest",
|
||||
)
|
||||
decision = _policy(enforce_scope=True).decide(request)
|
||||
assert decision.allowed is False
|
||||
assert decision.reason == "authz_denied"
|
||||
Loading…
Add table
Add a link
Reference in a new issue