vouch-protocol 2.0.0__tar.gz → 2.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {vouch_protocol-2.0.0/vouch_protocol.egg-info → vouch_protocol-2.0.1}/PKG-INFO +11 -5
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/README.md +5 -3
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/pyproject.toml +4 -2
- vouch_protocol-2.0.1/tests/test_caveats.py +169 -0
- vouch_protocol-2.0.1/tests/test_deliberation.py +179 -0
- vouch_protocol-2.0.1/tests/test_provenance.py +169 -0
- vouch_protocol-2.0.1/tests/test_reasoning.py +279 -0
- vouch_protocol-2.0.1/tests/test_robot_access.py +181 -0
- vouch_protocol-2.0.1/tests/test_robot_fusion.py +121 -0
- vouch_protocol-2.0.1/tests/test_robot_wear.py +124 -0
- vouch_protocol-2.0.1/tests/test_transparency.py +224 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/__init__.py +166 -1
- vouch_protocol-2.0.1/vouch/caveats.py +305 -0
- vouch_protocol-2.0.1/vouch/deliberation.py +457 -0
- vouch_protocol-2.0.1/vouch/integrations/a2a.py +81 -0
- vouch_protocol-2.0.1/vouch/integrations/goose.py +132 -0
- vouch_protocol-2.0.1/vouch/integrations/langgraph.py +87 -0
- vouch_protocol-2.0.1/vouch/provenance.py +291 -0
- vouch_protocol-2.0.1/vouch/reasoning.py +502 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/__init__.py +46 -0
- vouch_protocol-2.0.1/vouch/robotics/access.py +268 -0
- vouch_protocol-2.0.1/vouch/robotics/fusion.py +200 -0
- vouch_protocol-2.0.1/vouch/robotics/wear.py +191 -0
- vouch_protocol-2.0.1/vouch/transparency.py +432 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1/vouch_protocol.egg-info}/PKG-INFO +11 -5
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch_protocol.egg-info/SOURCES.txt +19 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch_protocol.egg-info/requires.txt +7 -1
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/LICENSE +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/MANIFEST.in +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/setup.cfg +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_accountability.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_attribution.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_audio_bridge.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_audit_trail.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_auditor.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_autosign.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_behavioral_attestation.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_bridge.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_budget.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_c2pa_formats.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_cache.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_canary.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_cli_init.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_conformance.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_credential_gate.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_data_integrity.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_dx_sugar.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_fastapi_credential_gate.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_fleet.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_git_workflow.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_grade.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_hasura_integration.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_heartbeat.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_hybrid.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_hybrid_interop.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_jcs.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_jcs_interop.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_liveness_conformance.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_media_badge.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_media_native.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_merkle.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_multikey.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_nonce.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_proof_of_integration.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_quorum.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_receipts.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_recovery.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_reputation.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_reputation_aggregate.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_reputation_disputes.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_reputation_ledger.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_reputation_policy.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_reputation_portability.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_revocation.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_conformance.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_custody.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_embodiment.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_handshake_blackbox_passport.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_identity.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_lease_quorum.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_lifecycle.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_liveness_revocation.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_perception.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_pq.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_provenance_capability.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_robot_safety_record.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_scan.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_secure_key_custody.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_security_hardening.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_shield_guard.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_signer_vc.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_status_list.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_threshold.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_tool_registry.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_transport.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_transport_http_rendezvous.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_transport_rendezvous.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_transport_udna_sdk.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_trifecta.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_trust_check.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_trust_entropy.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_validator_server.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_vc.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/tests/test_verifier_vc.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/accountability.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/agent.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/async_verifier.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/attribution.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/attribution_cli.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/audio.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/audit_trail.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/auditor.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/autosign.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/behavioral_attestation.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/bridge/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/bridge/audio_routes.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/bridge/config.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/bridge/server.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/budget.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/cache.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/canary.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/cli.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/config.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/conformance.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/credential.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/data_integrity.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/data_integrity_hybrid.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/did_web.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/fleet.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/gate.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/grade.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/heartbeat.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/_common.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/adk.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/amnesia.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/autogen/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/autogpt/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/autogpt/commands.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/claude-code/settings.hooks.json +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/crewai/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/crewai/tool.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/fastapi.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/google.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/hasura/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/hasura/webhook.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/langchain/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/langchain/tool.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/mcp/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/mcp/server.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/n8n.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/streamlit/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/streamlit/seal.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/integrations/vertex_ai/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/jcs.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/keys.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/keystore.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/kms.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/liveness_conformance.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/mcp_guard.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/media/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/media/badge.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/media/c2pa.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/media/native.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/merkle.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/metrics.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/multikey.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/nonce.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/onboard.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/proof_of_integration.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/quorum.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/ratelimit.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/receipts.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/recovery.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/registry.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/reputation.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/reputation_aggregate.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/reputation_disputes.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/reputation_ledger.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/reputation_policy.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/reputation_portability.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/revocation.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/_signing.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/blackbox.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/capability.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/conformance.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/custody.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/embodiment.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/handshake.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/identity.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/lease.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/lifecycle.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/liveness.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/passport.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/perception.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/physical_quorum.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/pq.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/provenance.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/revocation.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/robotics/safety_record.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/scan/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/scan/detector.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/scan/patterns.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/seal.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/shield/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/shield/demo.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/shield/flight_recorder.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/shield/permissions.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/shield/shield.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/shield/trust_registry.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/signer.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/ssrf.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/status_list.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/status_list_fetcher.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/threshold.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/tool_registry.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/transport/__init__.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/transport/base.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/transport/did_key.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/transport/envelope.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/transport/http_rendezvous.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/transport/http_transport.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/transport/manager.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/transport/rendezvous.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/transport/udna.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/trifecta.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/trust_check.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/trust_entropy.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/validator_server.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/vc.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/verification.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch/verifier.py +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch_protocol.egg-info/dependency_links.txt +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch_protocol.egg-info/entry_points.txt +0 -0
- {vouch_protocol-2.0.0 → vouch_protocol-2.0.1}/vouch_protocol.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: vouch-protocol
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1
|
|
4
4
|
Summary: The Identity & Reputation Standard for AI Agents
|
|
5
5
|
Author-email: Vouch Protocol Contributors <hello@vouch-protocol.com>
|
|
6
6
|
License: Apache License 2.0
|
|
@@ -229,6 +229,10 @@ Provides-Extra: langchain
|
|
|
229
229
|
Requires-Dist: langchain>=0.1.0; extra == "langchain"
|
|
230
230
|
Provides-Extra: crewai
|
|
231
231
|
Requires-Dist: crewai>=0.1.0; extra == "crewai"
|
|
232
|
+
Provides-Extra: langgraph
|
|
233
|
+
Requires-Dist: langgraph>=0.1.0; extra == "langgraph"
|
|
234
|
+
Provides-Extra: goose
|
|
235
|
+
Requires-Dist: pyyaml>=6.0; extra == "goose"
|
|
232
236
|
Provides-Extra: autogen
|
|
233
237
|
Requires-Dist: pyautogen>=0.2.0; extra == "autogen"
|
|
234
238
|
Provides-Extra: mcp
|
|
@@ -273,7 +277,7 @@ Requires-Dist: base58>=2.1.0; extra == "dev"
|
|
|
273
277
|
Requires-Dist: qrcode>=7.4.0; extra == "dev"
|
|
274
278
|
Requires-Dist: c2pa-python>=0.5.0; extra == "dev"
|
|
275
279
|
Provides-Extra: all
|
|
276
|
-
Requires-Dist: vouch-protocol[autogen,crewai,dev,enterprise,langchain,mcp,server,streamlit,tracing,vertex]; extra == "all"
|
|
280
|
+
Requires-Dist: vouch-protocol[autogen,crewai,dev,enterprise,goose,langchain,langgraph,mcp,server,streamlit,tracing,vertex]; extra == "all"
|
|
277
281
|
Dynamic: license-file
|
|
278
282
|
|
|
279
283
|
# Vouch Protocol™
|
|
@@ -282,9 +286,9 @@ Dynamic: license-file
|
|
|
282
286
|
<img src="docs/assets/vouch-wordmark.png" alt="Vouch Protocol" width="400">
|
|
283
287
|
</p>
|
|
284
288
|
|
|
285
|
-
<sub><em>Vouch Protocol™
|
|
289
|
+
<sub><em>Vouch Protocol™ is a common-law trademark.</em></sub>
|
|
286
290
|
|
|
287
|
-
<p align="center">The Open Standard for Identity & Provenance of AI Agents</strong>
|
|
291
|
+
<p align="center"><strong>The Open Standard for Identity & Provenance of AI Agents</strong>
|
|
288
292
|
</p>
|
|
289
293
|
|
|
290
294
|
<p align="center">
|
|
@@ -377,8 +381,10 @@ Vouch is not one tool, it is a set of them. Here is the whole map.
|
|
|
377
381
|
### Framework integrations (new in v1.6.2)
|
|
378
382
|
Standalone packages that drop Vouch into the agent framework you already use. Each one issues a verifiable credential for a tool call, with optional delegation back to a human principal.
|
|
379
383
|
- **`vouch-langchain`** a LangChain tool that signs each tool call before it leaves the agent.
|
|
384
|
+
- **`vouch-langgraph`** signs LangGraph tool calls and graph nodes, so a signed trail runs across the whole graph.
|
|
380
385
|
- **`vouch-crewai`** a CrewAI tool, with supervisor-to-worker delegation that can only narrow authority, never widen it.
|
|
381
386
|
- **`vouch-a2a`** binds an A2A (Agent2Agent) Agent Card to a Vouch identity, so two agents can verify each other before they collaborate.
|
|
387
|
+
- **`vouch-goose`** registers the Vouch MCP server as an extension for Block's Goose agent, so a Goose session can sign and verify out of the box.
|
|
382
388
|
- **`vouch-mlflow`** signs an MLflow model artifact at registration time, bound to a content digest so any change to the weights breaks the signature.
|
|
383
389
|
- **`vouch-safetensors`** embeds a credential in a `.safetensors` header, complementary to OpenSSF Model Signing, so a model carries who produced it.
|
|
384
390
|
|
|
@@ -388,7 +394,7 @@ The standalone **`vouch-mcp`** package above ships alongside these in v1.6.2.
|
|
|
388
394
|
Python, TypeScript, and Go are the full reference implementations. A Rust core with idiomatic Swift, JVM (Java and Kotlin), .NET, and C wrappers shares one codebase, so every language produces byte-identical output, verified against shared test vectors. A WebAssembly build is included for the browser and the edge. See the table further down for status per language.
|
|
389
395
|
|
|
390
396
|
### Robots and embodied agents
|
|
391
|
-
A robot is an agent with a body, so identity and accountability matter even more once it can cause physical harm. The `vouch.robotics` module ships six capabilities on the same `eddsa-jcs-2022` credentials as the rest of Vouch: hardware-rooted identity (bound to a TPM or secure element, so it cannot be cloned to other hardware), model and config provenance (re-signable on every OTA update), physical capability scope (force, speed, a tighter cap near humans, allowed zones, and shift windows, checked before each actuation, with narrow-only delegation), a robot-to-robot trust handshake, an encrypted tamper-evident black box with a verifiable kill switch, and a scannable offline passport. All six are implemented in Python, TypeScript, Go, and the Rust core, which flows to the Swift, Kotlin/JVM, .NET, C/C++, and WebAssembly wrappers, so a robotics credential signed in one language verifies in every other.
|
|
397
|
+
A robot is an agent with a body, so identity and accountability matter even more once it can cause physical harm. The `vouch.robotics` module ships six capabilities on the same `eddsa-jcs-2022` credentials as the rest of Vouch: hardware-rooted identity (bound to a TPM or secure element, so it cannot be cloned to other hardware), model and config provenance (re-signable on every OTA update), physical capability scope (force, speed, a tighter cap near humans, allowed zones, and shift windows, checked before each actuation, with narrow-only delegation), a robot-to-robot trust handshake, an encrypted tamper-evident black box with a verifiable kill switch, and a scannable offline passport. All six are implemented in Python, TypeScript, Go, and the Rust core, which flows to the Swift, Kotlin/JVM, .NET, C/C++, and WebAssembly wrappers, so a robotics credential signed in one language verifies in every other. Fourteen further capabilities round out the set: a living-trust heartbeat (a signed per-interval motion summary whose trust decays unless it is renewed in-envelope), two-level credential revocation (per-credential status lists and whole-DID kill), an accountable safety record (a tamper-evident incident ledger summarized into a portable signed record), signed perception provenance (each captured sensor frame's hash bound to the robot's key and hash-linked, so a robot can prove what its sensors saw and a substituted frame is detectable), an offline delegation lease (a short-lived, scope-bounded grant a disconnected robot verifies and acts on with no network call, nesting across vendors), a physical quorum (a cryptographic two-person rule requiring M of N attested approvers for a high-consequence action), lifecycle credentials (ownership transfer that chains into a verifiable chain of custody, key rotation that forms a key history, and a signed decommission that retires the robot), a regulatory conformance profile (a machine-checkable mapping from a robot's credentials to the clauses of ISO 10218 and 15066, the EU Machinery Regulation, the EU AI Act, and UL 3300, with a deterministic checker and a signed conformance attestation), post-quantum signing by default (robot credentials sign with the hybrid classical-plus-ML-DSA-44 cryptosuite, with backward-compatible verification, so a robot stays unforgeable across its decade-long life), and cross-embodiment identity continuity (an agent identity that moves between robot bodies, with an embodiment credential re-binding to each body's hardware root and a continuity chain that proves the same accountable agent persisted, plus a fork check), a physical custody handoff chain (a signed record of who accepted custody of a task or object at each hop across human and robot actors, so an incident traces to the exact hop and an attested condition localizes damage to the holder responsible), bounded infrastructure access (an operator-signed grant naming a resource, its permitted operations, an optional zone, and a time window, which a robot pairs with a signed request the resource authorizes offline, so a robot can open a door or dock at a charger with a tamper-evident, attributable, shrink-only record), fused-sensor provenance (a signed attestation binding a robot's fused world model to the exact set of input frame hashes and the fusion method that produced it, so a manipulated fusion result or a dropped input is detectable and every fused input traces to a frame the robot recorded), and wear and degradation attestation (a robot signs its own wear level bound to its identity and hash-linked over time, and a deterministic rule narrows its physical capability scope as it degrades, so a worn robot operates inside a tighter, verifiable envelope than the static limit it shipped with), implemented across the same languages and pinned by the shared interop vector. See [docs/robotics.md](docs/robotics.md) and the defensive disclosures PAD-064 through PAD-070, PAD-076 through PAD-084, and PAD-087 through PAD-092.
|
|
392
398
|
|
|
393
399
|
### Inside your AI tools
|
|
394
400
|
- **Claude Skill**, **OpenAI Custom GPT**, and **Gemini Gem** packages that teach your AI assistant how to add Vouch to your code, running on your own AI subscription.
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
<img src="docs/assets/vouch-wordmark.png" alt="Vouch Protocol" width="400">
|
|
5
5
|
</p>
|
|
6
6
|
|
|
7
|
-
<sub><em>Vouch Protocol™
|
|
7
|
+
<sub><em>Vouch Protocol™ is a common-law trademark.</em></sub>
|
|
8
8
|
|
|
9
|
-
<p align="center">The Open Standard for Identity & Provenance of AI Agents</strong>
|
|
9
|
+
<p align="center"><strong>The Open Standard for Identity & Provenance of AI Agents</strong>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -99,8 +99,10 @@ Vouch is not one tool, it is a set of them. Here is the whole map.
|
|
|
99
99
|
### Framework integrations (new in v1.6.2)
|
|
100
100
|
Standalone packages that drop Vouch into the agent framework you already use. Each one issues a verifiable credential for a tool call, with optional delegation back to a human principal.
|
|
101
101
|
- **`vouch-langchain`** a LangChain tool that signs each tool call before it leaves the agent.
|
|
102
|
+
- **`vouch-langgraph`** signs LangGraph tool calls and graph nodes, so a signed trail runs across the whole graph.
|
|
102
103
|
- **`vouch-crewai`** a CrewAI tool, with supervisor-to-worker delegation that can only narrow authority, never widen it.
|
|
103
104
|
- **`vouch-a2a`** binds an A2A (Agent2Agent) Agent Card to a Vouch identity, so two agents can verify each other before they collaborate.
|
|
105
|
+
- **`vouch-goose`** registers the Vouch MCP server as an extension for Block's Goose agent, so a Goose session can sign and verify out of the box.
|
|
104
106
|
- **`vouch-mlflow`** signs an MLflow model artifact at registration time, bound to a content digest so any change to the weights breaks the signature.
|
|
105
107
|
- **`vouch-safetensors`** embeds a credential in a `.safetensors` header, complementary to OpenSSF Model Signing, so a model carries who produced it.
|
|
106
108
|
|
|
@@ -110,7 +112,7 @@ The standalone **`vouch-mcp`** package above ships alongside these in v1.6.2.
|
|
|
110
112
|
Python, TypeScript, and Go are the full reference implementations. A Rust core with idiomatic Swift, JVM (Java and Kotlin), .NET, and C wrappers shares one codebase, so every language produces byte-identical output, verified against shared test vectors. A WebAssembly build is included for the browser and the edge. See the table further down for status per language.
|
|
111
113
|
|
|
112
114
|
### Robots and embodied agents
|
|
113
|
-
A robot is an agent with a body, so identity and accountability matter even more once it can cause physical harm. The `vouch.robotics` module ships six capabilities on the same `eddsa-jcs-2022` credentials as the rest of Vouch: hardware-rooted identity (bound to a TPM or secure element, so it cannot be cloned to other hardware), model and config provenance (re-signable on every OTA update), physical capability scope (force, speed, a tighter cap near humans, allowed zones, and shift windows, checked before each actuation, with narrow-only delegation), a robot-to-robot trust handshake, an encrypted tamper-evident black box with a verifiable kill switch, and a scannable offline passport. All six are implemented in Python, TypeScript, Go, and the Rust core, which flows to the Swift, Kotlin/JVM, .NET, C/C++, and WebAssembly wrappers, so a robotics credential signed in one language verifies in every other.
|
|
115
|
+
A robot is an agent with a body, so identity and accountability matter even more once it can cause physical harm. The `vouch.robotics` module ships six capabilities on the same `eddsa-jcs-2022` credentials as the rest of Vouch: hardware-rooted identity (bound to a TPM or secure element, so it cannot be cloned to other hardware), model and config provenance (re-signable on every OTA update), physical capability scope (force, speed, a tighter cap near humans, allowed zones, and shift windows, checked before each actuation, with narrow-only delegation), a robot-to-robot trust handshake, an encrypted tamper-evident black box with a verifiable kill switch, and a scannable offline passport. All six are implemented in Python, TypeScript, Go, and the Rust core, which flows to the Swift, Kotlin/JVM, .NET, C/C++, and WebAssembly wrappers, so a robotics credential signed in one language verifies in every other. Fourteen further capabilities round out the set: a living-trust heartbeat (a signed per-interval motion summary whose trust decays unless it is renewed in-envelope), two-level credential revocation (per-credential status lists and whole-DID kill), an accountable safety record (a tamper-evident incident ledger summarized into a portable signed record), signed perception provenance (each captured sensor frame's hash bound to the robot's key and hash-linked, so a robot can prove what its sensors saw and a substituted frame is detectable), an offline delegation lease (a short-lived, scope-bounded grant a disconnected robot verifies and acts on with no network call, nesting across vendors), a physical quorum (a cryptographic two-person rule requiring M of N attested approvers for a high-consequence action), lifecycle credentials (ownership transfer that chains into a verifiable chain of custody, key rotation that forms a key history, and a signed decommission that retires the robot), a regulatory conformance profile (a machine-checkable mapping from a robot's credentials to the clauses of ISO 10218 and 15066, the EU Machinery Regulation, the EU AI Act, and UL 3300, with a deterministic checker and a signed conformance attestation), post-quantum signing by default (robot credentials sign with the hybrid classical-plus-ML-DSA-44 cryptosuite, with backward-compatible verification, so a robot stays unforgeable across its decade-long life), and cross-embodiment identity continuity (an agent identity that moves between robot bodies, with an embodiment credential re-binding to each body's hardware root and a continuity chain that proves the same accountable agent persisted, plus a fork check), a physical custody handoff chain (a signed record of who accepted custody of a task or object at each hop across human and robot actors, so an incident traces to the exact hop and an attested condition localizes damage to the holder responsible), bounded infrastructure access (an operator-signed grant naming a resource, its permitted operations, an optional zone, and a time window, which a robot pairs with a signed request the resource authorizes offline, so a robot can open a door or dock at a charger with a tamper-evident, attributable, shrink-only record), fused-sensor provenance (a signed attestation binding a robot's fused world model to the exact set of input frame hashes and the fusion method that produced it, so a manipulated fusion result or a dropped input is detectable and every fused input traces to a frame the robot recorded), and wear and degradation attestation (a robot signs its own wear level bound to its identity and hash-linked over time, and a deterministic rule narrows its physical capability scope as it degrades, so a worn robot operates inside a tighter, verifiable envelope than the static limit it shipped with), implemented across the same languages and pinned by the shared interop vector. See [docs/robotics.md](docs/robotics.md) and the defensive disclosures PAD-064 through PAD-070, PAD-076 through PAD-084, and PAD-087 through PAD-092.
|
|
114
116
|
|
|
115
117
|
### Inside your AI tools
|
|
116
118
|
- **Claude Skill**, **OpenAI Custom GPT**, and **Gemini Gem** packages that teach your AI assistant how to add Vouch to your code, running on your own AI subscription.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vouch-protocol"
|
|
7
|
-
version = "2.0.
|
|
7
|
+
version = "2.0.1"
|
|
8
8
|
description = "The Identity & Reputation Standard for AI Agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -43,6 +43,8 @@ dependencies = [
|
|
|
43
43
|
# Framework integrations
|
|
44
44
|
langchain = ["langchain>=0.1.0"]
|
|
45
45
|
crewai = ["crewai>=0.1.0"]
|
|
46
|
+
langgraph = ["langgraph>=0.1.0"]
|
|
47
|
+
goose = ["pyyaml>=6.0"]
|
|
46
48
|
autogen = ["pyautogen>=0.2.0"]
|
|
47
49
|
mcp = ["mcp>=0.1.0"]
|
|
48
50
|
vertex = ["google-cloud-aiplatform>=1.0.0"]
|
|
@@ -107,7 +109,7 @@ dev = [
|
|
|
107
109
|
|
|
108
110
|
# All optional dependencies
|
|
109
111
|
all = [
|
|
110
|
-
"vouch-protocol[langchain,crewai,autogen,mcp,vertex,streamlit,server,enterprise,tracing,dev]"
|
|
112
|
+
"vouch-protocol[langchain,crewai,langgraph,goose,autogen,mcp,vertex,streamlit,server,enterprise,tracing,dev]"
|
|
111
113
|
]
|
|
112
114
|
|
|
113
115
|
[project.urls]
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Tests for executable caveats in delegation chains."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from vouch import Signer, generate_identity
|
|
6
|
+
from vouch.caveats import (
|
|
7
|
+
REASON_BROKEN_CHAIN,
|
|
8
|
+
REASON_BUDGET_EXCEEDED,
|
|
9
|
+
REASON_INVALID_LINK_PROOF,
|
|
10
|
+
REASON_UNKNOWN_TYPE,
|
|
11
|
+
REASON_UNROOTED,
|
|
12
|
+
CaveatError,
|
|
13
|
+
allowlist,
|
|
14
|
+
build_capability,
|
|
15
|
+
caveat,
|
|
16
|
+
chain_caveats,
|
|
17
|
+
evaluate_caveat,
|
|
18
|
+
flag_true,
|
|
19
|
+
incident_gate,
|
|
20
|
+
rate_limit,
|
|
21
|
+
running_total_ceiling,
|
|
22
|
+
time_window,
|
|
23
|
+
value_ceiling,
|
|
24
|
+
verify_capability,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _id(domain):
|
|
29
|
+
kp = generate_identity(domain=domain)
|
|
30
|
+
return kp, Signer(private_key=kp.private_key_jwk, did=kp.did)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TestEvaluateCaveat:
|
|
34
|
+
def test_value_ceiling(self):
|
|
35
|
+
c = value_ceiling("c", field="amount", limit=200)
|
|
36
|
+
assert evaluate_caveat(c, {"amount": 200}) == (True, None)
|
|
37
|
+
assert evaluate_caveat(c, {"amount": 201})[0] is False
|
|
38
|
+
|
|
39
|
+
def test_running_total_ceiling(self):
|
|
40
|
+
c = running_total_ceiling("c", limit=1000)
|
|
41
|
+
assert evaluate_caveat(c, {"amount": 100, "runningTotal": 800}) == (True, None)
|
|
42
|
+
assert evaluate_caveat(c, {"amount": 300, "runningTotal": 800})[0] is False
|
|
43
|
+
|
|
44
|
+
def test_time_window(self):
|
|
45
|
+
c = time_window("c", start=9, end=17)
|
|
46
|
+
assert evaluate_caveat(c, {"hour": 9})[0] is True
|
|
47
|
+
assert evaluate_caveat(c, {"hour": 17})[0] is False
|
|
48
|
+
assert evaluate_caveat(c, {"hour": 8})[0] is False
|
|
49
|
+
|
|
50
|
+
def test_allowlist(self):
|
|
51
|
+
c = allowlist("c", field="region", values=["eu", "us"])
|
|
52
|
+
assert evaluate_caveat(c, {"region": "eu"})[0] is True
|
|
53
|
+
assert evaluate_caveat(c, {"region": "apac"})[0] is False
|
|
54
|
+
|
|
55
|
+
def test_flag_and_incident(self):
|
|
56
|
+
assert evaluate_caveat(flag_true("c", field="shipped"), {"shipped": True})[0] is True
|
|
57
|
+
assert evaluate_caveat(flag_true("c", field="shipped"), {"shipped": False})[0] is False
|
|
58
|
+
assert evaluate_caveat(incident_gate("c"), {"incidentActive": True})[0] is True
|
|
59
|
+
assert evaluate_caveat(incident_gate("c"), {})[0] is False
|
|
60
|
+
|
|
61
|
+
def test_rate_limit(self):
|
|
62
|
+
c = rate_limit("c", max_count=3)
|
|
63
|
+
assert evaluate_caveat(c, {"recentCount": 2})[0] is True
|
|
64
|
+
assert evaluate_caveat(c, {"recentCount": 3})[0] is False
|
|
65
|
+
|
|
66
|
+
def test_unknown_type_denies(self):
|
|
67
|
+
allow, reason = evaluate_caveat({"id": "x", "type": "does_not_exist", "params": {}}, {})
|
|
68
|
+
assert allow is False and reason == REASON_UNKNOWN_TYPE
|
|
69
|
+
|
|
70
|
+
def test_deterministic(self):
|
|
71
|
+
c = value_ceiling("c", limit=10)
|
|
72
|
+
ctx = {"amount": 5}
|
|
73
|
+
assert evaluate_caveat(c, ctx) == evaluate_caveat(c, ctx)
|
|
74
|
+
|
|
75
|
+
def test_caveat_builder_validates(self):
|
|
76
|
+
with pytest.raises(CaveatError):
|
|
77
|
+
caveat("", "value_ceiling")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TestChainAndAccumulation:
|
|
81
|
+
def _chain(self):
|
|
82
|
+
skp, store = _id("store.example.com")
|
|
83
|
+
mkp, mgr = _id("manager.example.com")
|
|
84
|
+
akp, agent = _id("agent.example.com")
|
|
85
|
+
link1 = build_capability(
|
|
86
|
+
store,
|
|
87
|
+
to=mgr.get_did(),
|
|
88
|
+
attenuation={"action": "refund"},
|
|
89
|
+
caveats=[flag_true("shipped-only", field="shipped")],
|
|
90
|
+
)
|
|
91
|
+
link2 = build_capability(
|
|
92
|
+
mgr,
|
|
93
|
+
to=agent.get_did(),
|
|
94
|
+
attenuation={"action": "refund", "resource": "usd:<=200"},
|
|
95
|
+
caveats=[value_ceiling("under-200", field="amount", limit=200)],
|
|
96
|
+
parent=link1,
|
|
97
|
+
)
|
|
98
|
+
keys = {
|
|
99
|
+
store.get_did(): skp.public_key_jwk,
|
|
100
|
+
mgr.get_did(): mkp.public_key_jwk,
|
|
101
|
+
agent.get_did(): akp.public_key_jwk,
|
|
102
|
+
}
|
|
103
|
+
return [link1, link2], keys, store.get_did()
|
|
104
|
+
|
|
105
|
+
def test_accumulates_both_caveats(self):
|
|
106
|
+
chain, _, _ = self._chain()
|
|
107
|
+
assert [c["id"] for c in chain_caveats(chain)] == ["shipped-only", "under-200"]
|
|
108
|
+
|
|
109
|
+
def test_all_caveats_satisfied_allows(self):
|
|
110
|
+
chain, keys, root = self._chain()
|
|
111
|
+
assert (
|
|
112
|
+
verify_capability(chain, keys.get, {"shipped": True, "amount": 120}, root_issuer=root)
|
|
113
|
+
is None
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
def test_ancestor_caveat_binds_descendant(self):
|
|
117
|
+
chain, keys, root = self._chain()
|
|
118
|
+
# 'shipped-only' came from the STORE (root); it still blocks the agent.
|
|
119
|
+
r = verify_capability(chain, keys.get, {"shipped": False, "amount": 120}, root_issuer=root)
|
|
120
|
+
assert r == "caveat_denied:shipped-only"
|
|
121
|
+
|
|
122
|
+
def test_own_caveat_blocks(self):
|
|
123
|
+
chain, keys, root = self._chain()
|
|
124
|
+
r = verify_capability(chain, keys.get, {"shipped": True, "amount": 5000}, root_issuer=root)
|
|
125
|
+
assert r == "caveat_denied:under-200"
|
|
126
|
+
|
|
127
|
+
def test_non_removal_via_root_check(self):
|
|
128
|
+
chain, keys, root = self._chain()
|
|
129
|
+
# Present only the leaf link, hoping 'shipped-only' vanishes.
|
|
130
|
+
r = verify_capability(
|
|
131
|
+
[chain[1]], keys.get, {"shipped": False, "amount": 120}, root_issuer=root
|
|
132
|
+
)
|
|
133
|
+
assert r == REASON_UNROOTED
|
|
134
|
+
|
|
135
|
+
def test_broken_parent_linkage(self):
|
|
136
|
+
# Two validly-signed links whose parent pointer does not match the
|
|
137
|
+
# predecessor actually presented: linkage is broken though both proofs pass.
|
|
138
|
+
skp, store = _id("store.example.com")
|
|
139
|
+
mkp, mgr = _id("manager.example.com")
|
|
140
|
+
akp, agent = _id("agent.example.com")
|
|
141
|
+
real_parent = build_capability(store, to=mgr.get_did(), attenuation={"action": "refund"})
|
|
142
|
+
other_parent = build_capability(store, to=mgr.get_did(), attenuation={"action": "read"})
|
|
143
|
+
leaf = build_capability(
|
|
144
|
+
mgr, to=agent.get_did(), attenuation={"action": "refund"}, parent=real_parent
|
|
145
|
+
)
|
|
146
|
+
keys = {store.get_did(): skp.public_key_jwk, mgr.get_did(): mkp.public_key_jwk}
|
|
147
|
+
# Present other_parent (valid) in place of real_parent: leaf.parent.digest mismatches.
|
|
148
|
+
r = verify_capability([other_parent, leaf], keys.get, {}, root_issuer=store.get_did())
|
|
149
|
+
assert r == REASON_BROKEN_CHAIN
|
|
150
|
+
|
|
151
|
+
def test_wrong_key_fails_link_proof(self):
|
|
152
|
+
chain, keys, root = self._chain()
|
|
153
|
+
bad = dict(keys)
|
|
154
|
+
# point the store's DID at a different key
|
|
155
|
+
okp, _ = _id("other.example.com")
|
|
156
|
+
bad[chain[0]["issuer"]] = okp.public_key_jwk
|
|
157
|
+
r = verify_capability(chain, bad.get, {"shipped": True, "amount": 120}, root_issuer=root)
|
|
158
|
+
assert r == REASON_INVALID_LINK_PROOF
|
|
159
|
+
|
|
160
|
+
def test_budget_exceeded(self):
|
|
161
|
+
chain, keys, root = self._chain()
|
|
162
|
+
r = verify_capability(
|
|
163
|
+
chain, keys.get, {"shipped": True, "amount": 120}, root_issuer=root, max_caveats=1
|
|
164
|
+
)
|
|
165
|
+
assert r == REASON_BUDGET_EXCEEDED
|
|
166
|
+
|
|
167
|
+
def test_empty_chain_rejected(self):
|
|
168
|
+
with pytest.raises(CaveatError):
|
|
169
|
+
verify_capability([], {}.get, {})
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Tests for Proof of Deliberation (two-phase deliberated execution)."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime, timedelta, timezone
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from vouch import Signer, generate_identity
|
|
8
|
+
from vouch.deliberation import (
|
|
9
|
+
CLASS_IRREVERSIBLE_FINANCIAL,
|
|
10
|
+
CLASS_REVERSIBLE,
|
|
11
|
+
EXECUTE_TYPE,
|
|
12
|
+
INTENT_TYPE,
|
|
13
|
+
REASON_INTENT_MISMATCH,
|
|
14
|
+
REASON_INVALID_PROOF,
|
|
15
|
+
REASON_UNAUTHORIZED_EXECUTOR,
|
|
16
|
+
REASON_VETOED,
|
|
17
|
+
REASON_WINDOW_NOT_ELAPSED,
|
|
18
|
+
VETO_TYPE,
|
|
19
|
+
DeliberationError,
|
|
20
|
+
action_digest,
|
|
21
|
+
check_execution,
|
|
22
|
+
commit_intent,
|
|
23
|
+
execute,
|
|
24
|
+
requires_window,
|
|
25
|
+
veto_intent,
|
|
26
|
+
verify_execution,
|
|
27
|
+
verify_intent,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
WIRE = {"action": "transfer_funds", "target": "acct:vendor-1", "resource": "usd:5000"}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _identity(domain="agent.example.com"):
|
|
34
|
+
kp = generate_identity(domain=domain)
|
|
35
|
+
return kp, Signer(private_key=kp.private_key_jwk, did=kp.did)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _committed(agent, controller_did, opened, min_seconds=900):
|
|
39
|
+
return commit_intent(
|
|
40
|
+
agent,
|
|
41
|
+
intent=WIRE,
|
|
42
|
+
reversibility_class=CLASS_IRREVERSIBLE_FINANCIAL,
|
|
43
|
+
min_seconds=min_seconds,
|
|
44
|
+
veto_authorities=[controller_did],
|
|
45
|
+
opens_at=opened,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class TestClasses:
|
|
50
|
+
def test_reversible_needs_no_window(self):
|
|
51
|
+
assert requires_window(CLASS_REVERSIBLE) is False
|
|
52
|
+
assert requires_window(CLASS_IRREVERSIBLE_FINANCIAL) is True
|
|
53
|
+
|
|
54
|
+
def test_irreversible_requires_positive_window(self):
|
|
55
|
+
_, agent = _identity()
|
|
56
|
+
with pytest.raises(DeliberationError):
|
|
57
|
+
commit_intent(
|
|
58
|
+
agent, intent=WIRE, reversibility_class=CLASS_IRREVERSIBLE_FINANCIAL, min_seconds=0
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def test_unknown_class_rejected(self):
|
|
62
|
+
_, agent = _identity()
|
|
63
|
+
with pytest.raises(DeliberationError):
|
|
64
|
+
commit_intent(agent, intent=WIRE, reversibility_class="whatever", min_seconds=10)
|
|
65
|
+
|
|
66
|
+
def test_intent_needs_action_target(self):
|
|
67
|
+
_, agent = _identity()
|
|
68
|
+
with pytest.raises(DeliberationError):
|
|
69
|
+
commit_intent(agent, intent={"action": "x"}, reversibility_class=CLASS_REVERSIBLE)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class TestIntentAndVeto:
|
|
73
|
+
def test_commit_and_verify_intent(self):
|
|
74
|
+
akp, agent = _identity()
|
|
75
|
+
_, ctrl = _identity("controller.example.com")
|
|
76
|
+
intent = _committed(agent, ctrl.get_did(), datetime.now(timezone.utc))
|
|
77
|
+
assert INTENT_TYPE in intent["type"]
|
|
78
|
+
ok, subject = verify_intent(intent, akp.public_key_jwk)
|
|
79
|
+
assert ok and subject["actionDigest"]["digest"] == action_digest(WIRE)
|
|
80
|
+
|
|
81
|
+
def test_veto_binds_to_intent(self):
|
|
82
|
+
_, agent = _identity()
|
|
83
|
+
_, ctrl = _identity("controller.example.com")
|
|
84
|
+
intent = _committed(agent, ctrl.get_did(), datetime.now(timezone.utc))
|
|
85
|
+
veto = veto_intent(ctrl, intent_credential=intent, reason="no")
|
|
86
|
+
assert VETO_TYPE in veto["type"]
|
|
87
|
+
assert veto["credentialSubject"]["intentDigest"]["digest"] == action_digest(WIRE)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class TestExecution:
|
|
91
|
+
def test_clean_window_accepted(self):
|
|
92
|
+
akp, agent = _identity()
|
|
93
|
+
_, ctrl = _identity("controller.example.com")
|
|
94
|
+
opened = datetime.now(timezone.utc)
|
|
95
|
+
intent = _committed(agent, ctrl.get_did(), opened)
|
|
96
|
+
ex = execute(agent, intent_credential=intent, closed_at=opened + timedelta(seconds=901))
|
|
97
|
+
assert EXECUTE_TYPE in ex["type"]
|
|
98
|
+
assert check_execution(ex, intent, akp.public_key_jwk) is None
|
|
99
|
+
ok, subject = verify_execution(ex, intent, akp.public_key_jwk)
|
|
100
|
+
assert ok and subject["intent"] == WIRE
|
|
101
|
+
|
|
102
|
+
def test_execute_too_early_rejected(self):
|
|
103
|
+
akp, agent = _identity()
|
|
104
|
+
_, ctrl = _identity("controller.example.com")
|
|
105
|
+
opened = datetime.now(timezone.utc)
|
|
106
|
+
intent = _committed(agent, ctrl.get_did(), opened)
|
|
107
|
+
ex = execute(agent, intent_credential=intent, closed_at=opened + timedelta(seconds=60))
|
|
108
|
+
assert check_execution(ex, intent, akp.public_key_jwk) == REASON_WINDOW_NOT_ELAPSED
|
|
109
|
+
|
|
110
|
+
def test_veto_blocks_even_after_window(self):
|
|
111
|
+
akp, agent = _identity()
|
|
112
|
+
ckp, ctrl = _identity("controller.example.com")
|
|
113
|
+
opened = datetime.now(timezone.utc)
|
|
114
|
+
intent = _committed(agent, ctrl.get_did(), opened)
|
|
115
|
+
veto = veto_intent(ctrl, intent_credential=intent, reason="over threshold")
|
|
116
|
+
ex = execute(agent, intent_credential=intent, closed_at=opened + timedelta(seconds=1200))
|
|
117
|
+
reason = check_execution(
|
|
118
|
+
ex,
|
|
119
|
+
intent,
|
|
120
|
+
akp.public_key_jwk,
|
|
121
|
+
vetoes=[veto],
|
|
122
|
+
veto_public_keys={ctrl.get_did(): ckp.public_key_jwk},
|
|
123
|
+
)
|
|
124
|
+
assert reason == REASON_VETOED
|
|
125
|
+
|
|
126
|
+
def test_veto_from_unlisted_authority_ignored(self):
|
|
127
|
+
akp, agent = _identity()
|
|
128
|
+
_, ctrl = _identity("controller.example.com")
|
|
129
|
+
rkp, rogue = _identity("rogue.example.com")
|
|
130
|
+
opened = datetime.now(timezone.utc)
|
|
131
|
+
intent = _committed(agent, ctrl.get_did(), opened) # only ctrl may veto
|
|
132
|
+
rogue_veto = veto_intent(rogue, intent_credential=intent, reason="I object")
|
|
133
|
+
ex = execute(agent, intent_credential=intent, closed_at=opened + timedelta(seconds=1000))
|
|
134
|
+
reason = check_execution(
|
|
135
|
+
ex,
|
|
136
|
+
intent,
|
|
137
|
+
akp.public_key_jwk,
|
|
138
|
+
vetoes=[rogue_veto],
|
|
139
|
+
veto_public_keys={rogue.get_did(): rkp.public_key_jwk},
|
|
140
|
+
)
|
|
141
|
+
assert reason is None # rogue is not in vetoAuthorities
|
|
142
|
+
|
|
143
|
+
def test_unauthorized_executor_rejected(self):
|
|
144
|
+
akp, agent = _identity()
|
|
145
|
+
_, ctrl = _identity("controller.example.com")
|
|
146
|
+
bkp, bob = _identity("bob.example.com")
|
|
147
|
+
opened = datetime.now(timezone.utc)
|
|
148
|
+
intent = _committed(agent, ctrl.get_did(), opened)
|
|
149
|
+
# Bob (not the intent issuer) signs an execute for the same intent.
|
|
150
|
+
ex = execute(bob, intent_credential=intent, closed_at=opened + timedelta(seconds=1000))
|
|
151
|
+
assert check_execution(ex, intent, bkp.public_key_jwk) == REASON_UNAUTHORIZED_EXECUTOR
|
|
152
|
+
|
|
153
|
+
def test_tampered_action_rejected(self):
|
|
154
|
+
akp, agent = _identity()
|
|
155
|
+
_, ctrl = _identity("controller.example.com")
|
|
156
|
+
opened = datetime.now(timezone.utc)
|
|
157
|
+
intent = _committed(agent, ctrl.get_did(), opened)
|
|
158
|
+
ex = execute(agent, intent_credential=intent, closed_at=opened + timedelta(seconds=1000))
|
|
159
|
+
# Re-sign an execute whose embedded intent was changed to a bigger amount.
|
|
160
|
+
ex["credentialSubject"]["intent"] = {
|
|
161
|
+
"action": "transfer_funds",
|
|
162
|
+
"target": "acct:vendor-1",
|
|
163
|
+
"resource": "usd:999999",
|
|
164
|
+
}
|
|
165
|
+
from vouch import data_integrity # noqa: PLC0415
|
|
166
|
+
|
|
167
|
+
ex["proof"] = data_integrity.build_proof(
|
|
168
|
+
ex, agent._raw_priv, agent.verification_method_id()
|
|
169
|
+
)
|
|
170
|
+
assert check_execution(ex, intent, akp.public_key_jwk) == REASON_INTENT_MISMATCH
|
|
171
|
+
|
|
172
|
+
def test_wrong_executor_key_fails_proof(self):
|
|
173
|
+
akp, agent = _identity()
|
|
174
|
+
okp, _ = _identity("other.example.com")
|
|
175
|
+
_, ctrl = _identity("controller.example.com")
|
|
176
|
+
opened = datetime.now(timezone.utc)
|
|
177
|
+
intent = _committed(agent, ctrl.get_did(), opened)
|
|
178
|
+
ex = execute(agent, intent_credential=intent, closed_at=opened + timedelta(seconds=1000))
|
|
179
|
+
assert check_execution(ex, intent, okp.public_key_jwk) == REASON_INVALID_PROOF
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"""Tests for inference provenance (bind an output to its model and context)."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from vouch import Signer, generate_identity
|
|
6
|
+
from vouch.provenance import (
|
|
7
|
+
PROVENANCE_TYPE,
|
|
8
|
+
REASON_CONTEXT_ROOT_MISMATCH,
|
|
9
|
+
REASON_MISSING_BINDING,
|
|
10
|
+
REASON_OUTPUT_MISMATCH,
|
|
11
|
+
REASON_WEIGHTS_MISMATCH,
|
|
12
|
+
ProvenanceError,
|
|
13
|
+
check_replay,
|
|
14
|
+
context_root,
|
|
15
|
+
output_digest,
|
|
16
|
+
sign_inference_provenance,
|
|
17
|
+
verify_context,
|
|
18
|
+
verify_inference_provenance,
|
|
19
|
+
weights_hash,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
CONTEXT = [
|
|
23
|
+
{"source": "policy://refunds/v4", "text": "Refunds allowed within 30 days."},
|
|
24
|
+
{"source": "order://A-1007", "text": "Delivered 2026-06-20. Amount 120 USD."},
|
|
25
|
+
]
|
|
26
|
+
OUTPUT = {"action": "approve_refund", "order": "A-1007", "amount": "usd:120"}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _identity(domain="agent.example.com"):
|
|
30
|
+
kp = generate_identity(domain=domain)
|
|
31
|
+
return kp, Signer(private_key=kp.private_key_jwk, did=kp.did)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestHelpers:
|
|
35
|
+
def test_output_digest_deterministic(self):
|
|
36
|
+
assert output_digest(OUTPUT) == output_digest(dict(OUTPUT))
|
|
37
|
+
|
|
38
|
+
def test_context_root_order_sensitive(self):
|
|
39
|
+
assert context_root(CONTEXT) != context_root(list(reversed(CONTEXT)))
|
|
40
|
+
|
|
41
|
+
def test_context_root_empty_rejected(self):
|
|
42
|
+
with pytest.raises(ProvenanceError):
|
|
43
|
+
context_root([])
|
|
44
|
+
|
|
45
|
+
def test_weights_hash_multibase(self):
|
|
46
|
+
assert weights_hash(b"abc").startswith("u")
|
|
47
|
+
|
|
48
|
+
def test_to_bytes_type_error(self):
|
|
49
|
+
with pytest.raises(ProvenanceError):
|
|
50
|
+
output_digest(123)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestSignAndVerify:
|
|
54
|
+
def test_full_flow(self):
|
|
55
|
+
kp, agent = _identity()
|
|
56
|
+
wh = weights_hash(b"model-weights")
|
|
57
|
+
cred = sign_inference_provenance(
|
|
58
|
+
agent,
|
|
59
|
+
output=OUTPUT,
|
|
60
|
+
model_weights_hash=wh,
|
|
61
|
+
context_chunks=CONTEXT,
|
|
62
|
+
sampler={"seed": 42, "temperature": 0.0},
|
|
63
|
+
)
|
|
64
|
+
assert PROVENANCE_TYPE in cred["type"]
|
|
65
|
+
ok, subject = verify_inference_provenance(cred, kp.public_key_jwk)
|
|
66
|
+
assert ok
|
|
67
|
+
assert subject["provenance"]["modelWeightsHash"] == wh
|
|
68
|
+
assert subject["provenance"]["contextRoot"]["root"] == context_root(CONTEXT)
|
|
69
|
+
assert subject["outputDigest"]["digest"] == output_digest(OUTPUT)
|
|
70
|
+
|
|
71
|
+
def test_requires_a_binding(self):
|
|
72
|
+
_, agent = _identity()
|
|
73
|
+
with pytest.raises(ProvenanceError):
|
|
74
|
+
sign_inference_provenance(agent, output=OUTPUT) # no weights, no context
|
|
75
|
+
|
|
76
|
+
def test_weights_only_is_valid(self):
|
|
77
|
+
kp, agent = _identity()
|
|
78
|
+
cred = sign_inference_provenance(
|
|
79
|
+
agent, output=OUTPUT, model_weights_hash=weights_hash(b"m")
|
|
80
|
+
)
|
|
81
|
+
ok, _ = verify_inference_provenance(cred, kp.public_key_jwk)
|
|
82
|
+
assert ok
|
|
83
|
+
|
|
84
|
+
def test_context_only_is_valid(self):
|
|
85
|
+
kp, agent = _identity()
|
|
86
|
+
cred = sign_inference_provenance(agent, output=OUTPUT, context_chunks=CONTEXT)
|
|
87
|
+
ok, _ = verify_inference_provenance(cred, kp.public_key_jwk)
|
|
88
|
+
assert ok
|
|
89
|
+
|
|
90
|
+
def test_include_output_false_hides_plaintext(self):
|
|
91
|
+
kp, agent = _identity()
|
|
92
|
+
cred = sign_inference_provenance(
|
|
93
|
+
agent, output=OUTPUT, context_chunks=CONTEXT, include_output=False
|
|
94
|
+
)
|
|
95
|
+
ok, subject = verify_inference_provenance(cred, kp.public_key_jwk)
|
|
96
|
+
assert ok and "output" not in subject and subject["outputDigest"]["digest"]
|
|
97
|
+
|
|
98
|
+
def test_wrong_key_fails(self):
|
|
99
|
+
_, agent = _identity()
|
|
100
|
+
other_kp, _ = _identity("other.example.com")
|
|
101
|
+
cred = sign_inference_provenance(agent, output=OUTPUT, context_chunks=CONTEXT)
|
|
102
|
+
ok, _ = verify_inference_provenance(cred, other_kp.public_key_jwk)
|
|
103
|
+
assert ok is False
|
|
104
|
+
|
|
105
|
+
def test_tamper_breaks_proof(self):
|
|
106
|
+
kp, agent = _identity()
|
|
107
|
+
cred = sign_inference_provenance(agent, output=OUTPUT, context_chunks=CONTEXT)
|
|
108
|
+
cred["credentialSubject"]["output"]["amount"] = "usd:999999"
|
|
109
|
+
ok, _ = verify_inference_provenance(cred, kp.public_key_jwk)
|
|
110
|
+
assert ok is False
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TestContextVerification:
|
|
114
|
+
def _subject(self):
|
|
115
|
+
kp, agent = _identity()
|
|
116
|
+
cred = sign_inference_provenance(agent, output=OUTPUT, context_chunks=CONTEXT)
|
|
117
|
+
_, subject = verify_inference_provenance(cred, kp.public_key_jwk)
|
|
118
|
+
return subject
|
|
119
|
+
|
|
120
|
+
def test_same_context_reproduces(self):
|
|
121
|
+
ok, reason = verify_context(CONTEXT, self._subject())
|
|
122
|
+
assert ok and reason is None
|
|
123
|
+
|
|
124
|
+
def test_substituted_context_rejected(self):
|
|
125
|
+
forged = [{"source": "policy://refunds/v4", "text": "Refunds any time."}, CONTEXT[1]]
|
|
126
|
+
ok, reason = verify_context(forged, self._subject())
|
|
127
|
+
assert ok is False and reason == REASON_CONTEXT_ROOT_MISMATCH
|
|
128
|
+
|
|
129
|
+
def test_reordered_context_rejected(self):
|
|
130
|
+
ok, reason = verify_context(list(reversed(CONTEXT)), self._subject())
|
|
131
|
+
assert ok is False and reason == REASON_CONTEXT_ROOT_MISMATCH
|
|
132
|
+
|
|
133
|
+
def test_missing_binding_when_no_context_root(self):
|
|
134
|
+
kp, agent = _identity()
|
|
135
|
+
cred = sign_inference_provenance(
|
|
136
|
+
agent, output=OUTPUT, model_weights_hash=weights_hash(b"m")
|
|
137
|
+
)
|
|
138
|
+
_, subject = verify_inference_provenance(cred, kp.public_key_jwk)
|
|
139
|
+
ok, reason = verify_context(CONTEXT, subject)
|
|
140
|
+
assert ok is False and reason == REASON_MISSING_BINDING
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class TestReplay:
|
|
144
|
+
def _subject(self, wh):
|
|
145
|
+
kp, agent = _identity()
|
|
146
|
+
cred = sign_inference_provenance(
|
|
147
|
+
agent, output=OUTPUT, model_weights_hash=wh, context_chunks=CONTEXT
|
|
148
|
+
)
|
|
149
|
+
_, subject = verify_inference_provenance(cred, kp.public_key_jwk)
|
|
150
|
+
return subject
|
|
151
|
+
|
|
152
|
+
def test_matching_replay_passes(self):
|
|
153
|
+
wh = weights_hash(b"model")
|
|
154
|
+
assert check_replay(self._subject(wh), output=OUTPUT, model_weights_hash=wh) is None
|
|
155
|
+
|
|
156
|
+
def test_output_mismatch(self):
|
|
157
|
+
wh = weights_hash(b"model")
|
|
158
|
+
bad = {"action": "approve_refund", "order": "A-1007", "amount": "usd:999999"}
|
|
159
|
+
assert check_replay(self._subject(wh), output=bad) == REASON_OUTPUT_MISMATCH
|
|
160
|
+
|
|
161
|
+
def test_weights_mismatch(self):
|
|
162
|
+
wh = weights_hash(b"model")
|
|
163
|
+
assert (
|
|
164
|
+
check_replay(self._subject(wh), model_weights_hash=weights_hash(b"other"))
|
|
165
|
+
== REASON_WEIGHTS_MISMATCH
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def test_no_checks_supplied_passes(self):
|
|
169
|
+
assert check_replay(self._subject(weights_hash(b"model"))) is None
|