approval-engine/tests/test_tenant_provenance.py
tegwick e9e9f93317 APPROVAL-WP-0007-T01 done: required_assurance floor; executor ingress; alignment answer
- Optional required_assurance {"level": aal1|aal2|aal3} at issue, schema v7.
  Enforced at add_entry (403), claim (assurance_unsatisfied) and consume (409)
  from the verified token level only; inherited on supersession; legacy rows
  NULL. Served only when declared so undeclared claims keep exact bytes.
- deploy/networkpolicies.yaml: pod-scoped ingress for sso +
  app.kubernetes.io/name=bounded-executor on 8080 (hub 39a3bffa). Not applied.
- docs/decision-services-alignment-answer.md: owner answer to the NetKingdom
  decision services review (hub 81cafbf3): roles accepted, T02 split proposed,
  presentation_ref at create declined with an entry-level counter-proposal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 5502@bnt-lap001
Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
2026-10-31 23:07:05 +01:00

97 lines
4.5 KiB
Python

"""Verified tenant origin remains evidence, including absence and legacy rows."""
import json
import sqlite3
import pytest
from cryptography.hazmat.primitives.asymmetric import rsa
from approval_engine.api import App, call
from approval_engine.auth import JWTAuthenticator
from approval_engine.errors import StoreUnavailable
from approval_engine.store import Engine, LATEST_SCHEMA_VERSION
from tests.conftest import FROZEN, binding, validity
from tests.test_auth import _JWKS, _jwt
def authenticator():
private = rsa.generate_private_key(public_exponent=65537, key_size=2048)
auth = JWTAuthenticator(
issuer="https://keycape.example", audience="approval-engine",
jwks_url="https://keycape.example/jwks", jwks_client=_JWKS(private.public_key()),
)
return private, auth
@pytest.mark.parametrize("source", ["directory", "registration", "default", None])
def test_verified_provenance_survives_http_entry_restart_and_use(tmp_path, source):
private, auth = authenticator()
claims = dict(sub="human", tenant="tenant:platform", principal_type="human",
scope="approval:approve")
if source is not None:
claims["tenant_source"] = source
token = _jwt(private, **claims)
path = tmp_path / "approval.sqlite"
engine = Engine(path, clock=lambda: FROZEN)
obj = engine.create(binding(), validity(), human_control=True)
digest = obj.binding_digest
status, approved = call(App(engine, auth), "POST", f"/v1/approvals/{obj.id}/entries",
{"tenant_source": "spoofed"}, authorization="Bearer " + token)
assert status == 200
assert approved["entries"][0]["tenant_source"] == source
engine.close()
engine = Engine(path, clock=lambda: FROZEN, auto_migrate=False)
assert engine.get(obj.id).entries[0]["tenant_source"] == source
assert engine.get(obj.id).binding_digest == digest
assert "tenant_source" not in json.dumps(engine.claim(obj.id))
engine.consume(obj.id, digest)
rows = engine._conn().execute("SELECT class, payload_json FROM outbox").fetchall()
for row in rows:
payload = json.loads(row["payload_json"])
assert payload["details"]["threshold"]["approvers"][0]["tenant_source"] == source
assert {row["class"] for row in rows} == {"issuance", "use"}
engine.close()
@pytest.mark.parametrize("source", [None, "unknown", "", {}, [], 1])
def test_explicit_invalid_provenance_refuses_http_bind_without_mutation(engine, source):
private, auth = authenticator()
token = _jwt(private, sub="human", tenant="tenant:platform", principal_type="human",
scope="approval:approve", tenant_source=source)
obj = engine.create(binding(), validity(), human_control=True)
status, _ = call(App(engine, auth), "POST", f"/v1/approvals/{obj.id}/entries", {},
authorization="Bearer " + token)
assert status == 401
assert engine.get(obj.id).entries == []
assert engine._conn().execute("SELECT COUNT(*) FROM outbox").fetchone()[0] == 0
def test_v5_migration_preserves_unknown_provenance_and_existing_evidence(tmp_path):
path = tmp_path / "approval.sqlite"
engine = Engine(path, clock=lambda: FROZEN)
obj = engine.create(binding(), validity(), human_control=True)
engine.add_entry(obj.id, "human", principal_type="human")
digest = obj.binding_digest
evidence = engine._conn().execute("SELECT payload_json FROM outbox").fetchall()[0][0]
engine.close()
with sqlite3.connect(path) as conn:
conn.execute("ALTER TABLE entries DROP COLUMN tenant_source")
conn.execute("PRAGMA user_version=5")
with pytest.raises(StoreUnavailable, match="migrate"):
Engine(path, auto_migrate=False)
engine = Engine(path, clock=lambda: FROZEN)
assert engine.storage_status()["schema_version"] == LATEST_SCHEMA_VERSION == 7
assert engine.get(obj.id).entries[0]["tenant_source"] is None
assert engine.get(obj.id).binding_digest == digest
assert engine.get(obj.id).human_control is True
assert engine._conn().execute("SELECT payload_json FROM outbox").fetchone()[0] == evidence
engine.close()
def test_provenance_rolls_back_with_failed_outbox(engine):
obj = engine.create(binding(), validity(), human_control=True)
engine.fail_outbox = True
with pytest.raises(StoreUnavailable):
engine.add_entry(obj.id, "human", principal_type="human", tenant_source="directory")
engine.fail_outbox = False
assert engine.get(obj.id).entries == []
assert engine.get(obj.id).status == "requested"