"""Network-free contract tests for the AffixIO Hugging Face SDK.""" # Credit: @paparichens from __future__ import annotations from typing import Any import pytest from affix_huggingface import ( AdmissionChecks, AdmissionDenied, AdmissionProof, AffixHuggingFace, ModelRef, StreamNotComplete, ) class FakeAffixTransport: def __init__(self, *, allow: bool = True) -> None: self.allow = allow self.prove_calls: list[dict[str, Any]] = [] self.gate_calls: list[dict[str, Any]] = [] self.attest_calls: list[dict[str, Any]] = [] def close(self) -> None: return None def health(self) -> tuple[dict[str, Any], str | None]: return {"ok": True}, "req_health" def prove(self, **kwargs: Any) -> tuple[dict[str, Any], str | None]: self.prove_calls.append(kwargs) return {"proof": "proof_hex", "valid": True}, "req_prove" def gate(self, **kwargs: Any) -> tuple[dict[str, Any], str | None]: self.gate_calls.append(kwargs) return ( { "allow": self.allow, "reason_code": "ADMITTED" if self.allow else "INVALID_PROOF", "receipt_id": "rcpt_1", "proof_digest": "abc123", "proof_ref": "1:abc123", "policy_id": "gate.agent", "policy_version": "2026.08", "mode": kwargs["mode"], "spent": self.allow and kwargs["mode"] == "consume", "merkle_root": "root_1", "merkle_leaf_hash": "leaf_1", "attestation": {"algorithm": "ML-DSA-65"}, }, "req_gate", ) def attest(self, payload: dict[str, Any], **kwargs: Any) -> tuple[dict[str, Any], str | None]: self.attest_calls.append({"payload": payload, **kwargs}) return {"attestation": {"algorithm": "ML-DSA-65", "signature": "sig"}}, "req_attest" class FakeHfClient: def __init__(self) -> None: self.calls: list[dict[str, Any]] = [] def chat_completion( self, messages: list[dict[str, Any]], *, model: str, **kwargs: Any, ) -> dict[str, Any] | list[str]: self.calls.append( {"method": "chat_completion", "messages": messages, "model": model, **kwargs} ) if kwargs.get("stream"): return ["token-1", "token-2"] return {"id": "hf_response_1", "choices": []} def text_generation(self, prompt: str, *, model: str, **kwargs: Any) -> str | list[str]: self.calls.append( {"method": "text_generation", "prompt": prompt, "model": model, **kwargs} ) if kwargs.get("stream"): return ["first", "second"] return "generated" def feature_extraction( self, text: str | list[str], *, model: str, **kwargs: Any, ) -> list[list[float]]: self.calls.append( {"method": "feature_extraction", "text": text, "model": model, **kwargs} ) return [[0.1, 0.2]] def make_client( *, affix: FakeAffixTransport | None = None, hf: FakeHfClient | None = None, evidence_mode: str = "required", ) -> tuple[AffixHuggingFace, FakeAffixTransport, FakeHfClient]: affix = affix or FakeAffixTransport() hf = hf or FakeHfClient() client = AffixHuggingFace( affix_transport=affix, # type: ignore[arg-type] hf_client=hf, evidence_mode=evidence_mode, # type: ignore[arg-type] ) return client, affix, hf def test_model_binding_is_deterministic_and_excludes_content() -> None: model = ModelRef( model_id="org/model", revision="abc", provider="hf-inference", ) assert len(model.binding_digest) == 64 assert model.binding_digest == ModelRef( model_id="org/model", revision="abc", provider="hf-inference", ).binding_digest assert "prompt" not in model.binding() def test_denial_happens_before_hugging_face_call() -> None: client, _, hf = make_client(affix=FakeAffixTransport(allow=False)) with pytest.raises(AdmissionDenied) as exc: client.chat_completion( [{"role": "user", "content": "private prompt"}], model=ModelRef("org/model"), proof=AdmissionProof("bad-proof"), ) assert exc.value.reason_code == "INVALID_PROOF" assert hf.calls == [] def test_authorised_chat_returns_output_and_evidence() -> None: client, affix, hf = make_client() result = client.chat_completion( [{"role": "user", "content": "private prompt"}], model=ModelRef("org/model", revision="commit-sha"), proof=AdmissionProof("proof_hex"), max_tokens=20, ) assert result.output["id"] == "hf_response_1" assert result.receipt.gate.allow is True assert result.receipt.completion is not None assert hf.calls[0]["messages"][0]["content"] == "private prompt" assert "private prompt" not in repr(affix.gate_calls) assert "private prompt" not in repr(affix.attest_calls) assert affix.attest_calls[0]["payload"]["prompt_included"] is False assert affix.attest_calls[0]["payload"]["output_included"] is False def test_checks_use_remote_prove_then_consume() -> None: client, affix, _ = make_client(evidence_mode="off") client.text_generation( "private input", model=ModelRef("org/model"), checks=AdmissionChecks( entitled=True, data_route_allowed=True, controls_satisfied=True, ), ) assert affix.prove_calls[0]["fields"] == { "condition_greater1": True, "condition_greater2": True, "condition_greater3": True, } assert affix.gate_calls[0]["mode"] == "consume" assert "private input" not in repr(affix.prove_calls) assert "private input" not in repr(affix.gate_calls) def test_stream_attests_only_after_consumption() -> None: client, affix, _ = make_client() stream = client.stream_text_generation( "private input", model=ModelRef("org/model"), proof=AdmissionProof("proof_hex"), ) with pytest.raises(StreamNotComplete): _ = stream.final_receipt assert affix.attest_calls == [] assert list(stream) == ["first", "second"] assert stream.complete is True assert stream.final_receipt.completion is not None assert len(affix.attest_calls) == 1 def test_inspect_does_not_call_hugging_face() -> None: client, affix, hf = make_client(evidence_mode="off") receipt = client.inspect( ModelRef("org/model"), proof=AdmissionProof("proof_hex"), ) assert receipt.allow is True assert receipt.mode == "check" assert affix.gate_calls[0]["mode"] == "check" assert hf.calls == []